My homemade gear hobber

Discussion in 'Machine Modifications' started by Ross Donelly, Jul 13, 2018.

Help Support HMEM by donating using the link above.
  1. Jul 13, 2018 #1

    Ross Donelly

    Ross Donelly

    Ross Donelly

    Member

    Joined:
    Jul 10, 2018
    Messages:
    10
    Likes Received:
    21
    Gender:
    Male
    Location:
    california
    I thought some of you may be interested in this. I modified my milling machine to do gear hobbing. The goal is to turn the gear blank by exactly 1 tooth per revolution of the mill. To achieve this, I use a motor, shaft encoders on the mill motor and the gear blank motor, and an arduino running PID controller software to keep them exactly in sync. I also needed to tilt the mill head by the angle of the hob teeth, which I did with a thick shim.

    837.JPG

    Here's a picture of the hob cutting a plastic gear (just for a test). This was before I hardened the hob so I wanted to cut something soft.

    841.JPG

    And here are some gears I've made. Some spur gears and a worm gear (the worm was made on the lathe of course). I also used this to make the gears in my Webster engine.

    004.JPG

    Cheers,
    Ross.
     
    LorenOtto, tjwal, ukanduit and 5 others like this.
  2. Jul 13, 2018 #2

    natalefr

    natalefr

    natalefr

    Well-Known Member

    Joined:
    Sep 24, 2017
    Messages:
    146
    Likes Received:
    44
    Gender:
    Male
    Location:
    France
    Good job !
     
  3. Jul 14, 2018 #3

    Herbiev

    Herbiev

    Herbiev

    Administrator Staff Member Administrator Moderator Global Moderator

    Joined:
    Jan 6, 2011
    Messages:
    2,329
    Likes Received:
    294
    Great work. Thanks for sharing
     
  4. Jul 14, 2018 #4

    Engineville

    Engineville

    Engineville

    Member

    Joined:
    Apr 15, 2013
    Messages:
    12
    Likes Received:
    3
    This is a significant achievement. If you’re willing, please tell us more of how you made and configured this modified mini-mill. It appears that you have mounted an optical chopper/counter on the spindle of the mill, but is the spindle drive also a stepping motor? How do you synchronize the rotation of the mill spindle and the rotation of the a-axis holding the gear blank?
     
  5. Jul 14, 2018 #5

    Ross Donelly

    Ross Donelly

    Ross Donelly

    Member

    Joined:
    Jul 10, 2018
    Messages:
    10
    Likes Received:
    21
    Gender:
    Male
    Location:
    california
    Sure. There's a 2-bit opto-interruptor mounted on the mill motor frame and an interruptor disc with 18 notches mounted on the end of the motor shaft. The 2 bits feed the microcontroller, which is set up to count transitions. So the microcontroller knows how many notches have gone by and in which direction. Also knowing the gear ratio of the mill, it knows quite precisely the rotational position of the mill spindle. I have a similar setup on the gear blank motor (a-axis). Now given that we know both positions, we can measure the error in the a-axis position. The goal error is zero. A PID routine is set up to control the motor voltage and keep the motor moving at just the right speed that the error stays at zero (or as close as possible).

    The Arduino has PWM outputs that make motor control easy and accurate, and its inputs can trigger interrupts for counting. It's surprising how well this works. Once stabilized, it's accurate to within about 0.2 degrees. The one minor problem I've had is mill vibration occasionally getting into the opto interruptor, but I could fix that by mounting the LED/phototransistors more solidly.
     
  6. Jul 21, 2018 #6

    Wizard69

    Wizard69

    Wizard69

    Well-Known Member

    Joined:
    Mar 9, 2013
    Messages:
    1,188
    Likes Received:
    241
    Thanks for the post! This cries out for a video though, a thousand words and all.
     
  7. Jul 22, 2018 #7

    graywd

    graywd

    graywd

    New Member

    Joined:
    Feb 28, 2011
    Messages:
    2
    Likes Received:
    0
    great post but I'm missing something .... if the hob is spiral, why do you need to tilt the mill ---- or is the hob straight?
     
  8. Jul 22, 2018 #8

    Mechanicboy

    Mechanicboy

    Mechanicboy

    Well-Known Member

    Joined:
    Jul 10, 2013
    Messages:
    574
    Likes Received:
    201
    The hobbing gear cutter has spiral hence it has a angle, when you are creating the gear wheel, then the hob must be tilted to a right angle then the gear tooth is straight. Each hobbing gear cutter has angle marked on then one can set the correct angle on hobbing gear cutter to make straight tooth. If one want the helical gear, then angle + helical angle or angle - helical angle.
     
  9. Jul 22, 2018 #9

    graywd

    graywd

    graywd

    New Member

    Joined:
    Feb 28, 2011
    Messages:
    2
    Likes Received:
    0
    Thank you for the quick response. I had assumed that the spur blank rotated upward to match the helical rise of the hob so that the cutter always engaged the same spur. Part of my assumption was that if the head was tilted so that the hob cutter is flat, then the blank would be incremented only when the next spur is to be cut. I'll need to re-think this.
     
  10. Jul 23, 2018 #10

    kstrauss

    kstrauss

    kstrauss

    New Member

    Joined:
    Dec 2, 2010
    Messages:
    3
    Likes Received:
    0
    Did you use a standard Arduino PID module or did you roll your own?
     
  11. Jul 24, 2018 #11

    jsimpson

    jsimpson

    jsimpson

    New Member

    Joined:
    Jan 10, 2018
    Messages:
    3
    Likes Received:
    1
    Love it...
     
  12. Jul 24, 2018 #12

    Ross Donelly

    Ross Donelly

    Ross Donelly

    Member

    Joined:
    Jul 10, 2018
    Messages:
    10
    Likes Received:
    21
    Gender:
    Male
    Location:
    california
    I rolled my own. I'd post the program but I'm on vacation and don't have access to it. There's not much to it, mainly tuning the 3 coefficients.
     
  13. Jul 24, 2018 #13

    kstrauss

    kstrauss

    kstrauss

    New Member

    Joined:
    Dec 2, 2010
    Messages:
    3
    Likes Received:
    0
    Absolutely no rush so enjoy your vacation. I just like to see how others do things.
     
  14. Aug 1, 2018 #14

    necchiom

    necchiom

    necchiom

    Well-Known Member

    Joined:
    Jan 6, 2009
    Messages:
    76
    Likes Received:
    14
    Wow! Awesome job.
     
  15. Sep 23, 2018 #15

    Ross Donelly

    Ross Donelly

    Ross Donelly

    Member

    Joined:
    Jul 10, 2018
    Messages:
    10
    Likes Received:
    21
    Gender:
    Male
    Location:
    california
    Here's the hobber in operation:
     
  16. Sep 24, 2018 #16

    Engineville

    Engineville

    Engineville

    Member

    Joined:
    Apr 15, 2013
    Messages:
    12
    Likes Received:
    3
    I hope that you had a good vacation. Now that you’re back, thanks for posting a video of your modified mini-mill. This setup has much potential for CNC of small metal hobby parts without too much expense. Hence, I’m interested knowing more of you system design.
    An overall schematic illustrating how the different hardware components are electrically connected would be informative of how you control your mill. Did you really do this without the use of MACH4 or G-Code and only programming the Arduino and synchronizing to spindle and A-axis motors?
     
  17. Sep 24, 2018 #17

    Wizard69

    Wizard69

    Wizard69

    Well-Known Member

    Joined:
    Mar 9, 2013
    Messages:
    1,188
    Likes Received:
    241
    Hope vacation went well!!!

    As for the video that is icing on the cake.

    As for your code I'm a bit surprised that you found it easy. In my mind anyways keeping those two spindles in sync and not loosing timing would be a major headache.
     
  18. Sep 24, 2018 #18

    Ross Donelly

    Ross Donelly

    Ross Donelly

    Member

    Joined:
    Jul 10, 2018
    Messages:
    10
    Likes Received:
    21
    Gender:
    Male
    Location:
    california
    Yes, this was done without Mach4 or G-code. Below is a look inside the box. There is an Arduino Due clone and a motor driver (VNH5019 from pololu.com). I'm not sure if an Uno would be fast enough for this application.

    The orange/brown wires go to the motor position encoder. The green/blue wires go to the mill position encoder. The red wires go to the motor and PC power supply (0V/+5V/+12V). Everything runs off 5V except the motor which is 12V.

    20180923_195205.jpg

    Here is a picture of the mill encoder board. There's an IR LED and a 1.2K resistor as light source, and two IR phototransistors (with 10K pull-down resistors) right next to each-other.


    20180923_194821.jpg

    Here is a picture of the end of the motor, which is a cheap 12V 100RPM DC worm gear motor (PN01007-100, $64 from makermotor.com). You can see the two IR LEDs (with 1K resistors) press-fit into holes I drilled in the end of the motor case. There are also two hidden phototransistors at the other end of the motor, tucked in through holes I drilled in the side. The armature of the motor has 12 poles, and these act as an interruptor disc (the light shines through the gaps in the armature as it goes past). The beams need to be almost (but not quite) 180 degrees apart.

    20180923_201912.jpg

    This is the arduino sketch. It's all controlled from a PC via a USB cable. When it's running, it's constantly dumping info on the state of the PID and how synchronized it is.

    Code:
    #define HIGH_SPEED_MODE 0   // set if mill is in high speed gear setting
    #if HIGH_SPEED_MODE
    #define MREV  (18 * 4 * (30 / 14.0) * (21 / 20.0)) // 162
    #else
    #define MREV  (18 * 4 * (30 / 14.0) * (29 / 12.0)) // 372.857143
    #endif
    #define GREV  (12 * 4 * 56.0)
    #define NTEETH  23
    #define M       2.5          // motor power limit, 2.5 = full power
    
    #define MOTORPWM 13
    #define MOTORA 12
    #define MOTORB 11
    #define GPOS1  10
    #define GPOS2  9
    #define MPOS1  8
    #define MPOS2  7
    void setup()
    {
      pinMode(MOTORPWM, OUTPUT);
      pinMode(MOTORA, OUTPUT);
      pinMode(MOTORB, OUTPUT);
      pinMode(GPOS1, INPUT);
      pinMode(GPOS2, INPUT);
      pinMode(MPOS1, INPUT);
      pinMode(MPOS2, INPUT);
      digitalWrite(MOTORPWM, LOW);
      digitalWrite(MOTORA, LOW);
      digitalWrite(MOTORB, LOW);
      Serial.begin(115200);
      attachInterrupt(digitalPinToInterrupt(GPOS1), handle_gpos_change, CHANGE);
      attachInterrupt(digitalPinToInterrupt(GPOS2), handle_gpos_change, CHANGE);
      attachInterrupt(digitalPinToInterrupt(MPOS1), handle_mpos_change, CHANGE);
      attachInterrupt(digitalPinToInterrupt(MPOS2), handle_mpos_change, CHANGE);
    }
    
    void printSigned(double x)
    {
      if (x >= 0)
        Serial.print("+");
      Serial.print(x);
    }
    
    void get_gpos(int *ppos1, int *ppos2)
    {
      *ppos1 = digitalRead(GPOS1);
      *ppos2 = digitalRead(GPOS2);
    }
    
    void get_mpos(int *ppos1, int *ppos2)
    {
      *ppos1 = digitalRead(MPOS1);
      *ppos2 = digitalRead(MPOS2);
    }
    
    int enc(int pos1, int pos2)
    {
      if (pos1 == 0 && pos2 == 0)
        return 0;
      if (pos1 == 0 && pos2 == 1)
        return 1;
      if (pos1 == 1 && pos2 == 1)
        return 2;
      if (pos1 == 1 && pos2 == 0)
        return 3;
    }
    
    volatile int gposition = 0;
    volatile int gpos1 = -1, gpos2 = -1;
    void monitor_motor()
    {
      int pos1, pos2;
      get_gpos(&pos1, &pos2);
      if (pos1 != gpos1 || pos2 != gpos2)
      {
        int old_enc = enc(gpos1, gpos2);
        int new_enc = enc(pos1, pos2);
        if (old_enc != -1 && new_enc != old_enc)
        {
          if (new_enc == (old_enc + 1) % 4)
          {
            --gposition;
          }
          else if (new_enc == (old_enc + 3) % 4)
          {
            ++gposition;
          }
          else
          {
            Serial.print("skipped motor encoding ");
            Serial.print(old_enc);
            Serial.print(" -> ");
            Serial.print(new_enc);
            Serial.print("\n");
          }
        }
        gpos1 = pos1;
        gpos2 = pos2;
      }
    }
    
    volatile int mposition = 0;
    volatile int mpos1 = -1, mpos2 = -1;
    void monitor_mill()
    {
      int pos1, pos2;
      get_mpos(&pos1, &pos2);
      if (pos1 != mpos1 || pos2 != mpos2)
      {
        int old_enc = enc(mpos1, mpos2);
        int new_enc = enc(pos1, pos2);
        if (old_enc != -1 && new_enc != old_enc)
        {
          if (new_enc == (old_enc + 1) % 4)
          {
            ++mposition;
          }
          else if (new_enc == (old_enc + 3) % 4)
          {
            --mposition;
          }
          else
          {
            Serial.print("skipped milling encoding ");
            Serial.print(old_enc);
            Serial.print(" -> ");
            Serial.print(new_enc);
            Serial.print("\n");
          }
        }
        mpos1 = pos1;
        mpos2 = pos2;
      }
    }
    
    void handle_gpos_change()
    {
      monitor_motor();
    }
    
    void handle_mpos_change()
    {
      monitor_mill();
    }
    
    void set_speed(double speed)
    {
      int dir = (speed >= 0);
      double s = speed;
      if (s < 0)
        s = -s;
      int v = (int)(255 * s);
      digitalWrite(MOTORB, dir ? HIGH : LOW);
      digitalWrite(MOTORA, dir ? LOW : HIGH);
      analogWrite(MOTORPWM, v);
    }
    
    void run_at_speed(int dir, double speed, int duration)
    {
      Serial.print(gposition);
      Serial.print("\n");
      set_speed(dir ? speed : -speed);
      delayMicroseconds(duration);
      digitalWrite(MOTORA, LOW);
      digitalWrite(MOTORB, LOW);
    }
    
    void test_motor()
    {
      while (1)
      {
        for (int dir = 0; dir < 2; ++dir)
        {
          for (double speed = 0; speed <= 1.0; speed += 0.01)
            run_at_speed(dir, speed, 100000);
          run_at_speed(dir, 1.0, 1000000);
          for (double speed = 1.0; speed >= 0; speed -= 0.01)
            run_at_speed(dir, speed, 100000);
        }
      }
    }
    
    void test_mill_pos()
    {
      while (1)
      {
        Serial.print(mpos1);
        Serial.print(",");
        Serial.print(mpos2);
        Serial.print(" ");
        Serial.print(mposition);
        Serial.print("\n");
        delay(100);
      }
    }
    
    /* PID coeffs */
    /* crappy
    #define KP  0.001
    #define KI  0.0002
    #define KD  0.001
    */
    /* good */
    /*
    #define KP  0.05
    #define KI  0.0005
    #define KD  0
    */
    /* less overshoot */
    /*
    #define KP  0.05
    #define KI  0.0005
    #define KD  0.001
    */
    /* WITH MAX=2.5 */
    /* overshoot 43, final error 1 */
    /*
    #define KP  0.05
    #define KI  0.002
    #define KD  0
    */
    /* overshoot 7, final error <1 */
    /*
    #define KP  0.05
    #define KI  0.002
    #define KD  0.001
    */
    /* more aggressive, tuned after final assembly */
    #define KP  0.05
    #define KI  0.02
    #define KD  0
    
    void run_pid()
    {
      double tlast = 0;
      double sum_error = 0;
      double elast = 0;
      double spindle_pos_last = 0;
    
      /* PID */
      while (1)
      {
        delayMicroseconds(10000);
        double t = micros() * 1e-6;
        //double target_pos = 1000 + t * 500;
        double spindle_pos = mposition / MREV;
        double target_pos = (GREV * spindle_pos) / NTEETH;
        double error = target_pos - gposition;
        double dt = t - tlast;
        double derror = (error - elast) / dt;
        sum_error += error * dt;
        tlast = t;
        elast = error;
        double v = KP * error + KI * sum_error + KD * derror;
        double rpm = (spindle_pos - spindle_pos_last) * 60 / dt;
        spindle_pos_last = spindle_pos;
        if (v > M)
          v = M;
        if (v < -M)
          v = -M;
        set_speed(-v * 0.4);
        Serial.print("t=");
        Serial.print(t);
        Serial.print(" rpm=");
        Serial.print(rpm);
        Serial.print(" p=");
        Serial.print(gposition);
        Serial.print(" tgt=");
        Serial.print(target_pos);
        Serial.print(" error=");
        printSigned(error);
        Serial.print(" P=");[ATTACH=full]104370[/ATTACH] [ATTACH=full]104371[/ATTACH] [ATTACH=full]104372[/ATTACH]
        printSigned(KP * error);
        Serial.print(" I=");
        printSigned(KI * sum_error);
        Serial.print(" D=");
        printSigned(KD * derror);
        Serial.print(" v=");
        printSigned(v);
        Serial.print("\n");
      }
    }
    
    void loop()
    {
      //set_speed(1);
      //while (1)
      //  ;
      run_pid();
      //test_mill_pos();
    }
     
    Last edited: Sep 24, 2018
    tjwal likes this.
  19. Sep 24, 2018 #19

    Cogsy

    Cogsy

    Cogsy

    Moderator Moderator Global Moderator

    Joined:
    Jul 30, 2012
    Messages:
    2,226
    Likes Received:
    642
    Gender:
    Male
    Location:
    Perth, Western Australia
    Thanks for the code, I'll have a study of it later on. One thing that stuck out was all the serial reporting though. Obviously fine for you in this instance but you mention a Uno might not be fast enough. I was mucking around with a Uno a couple of months ago and had serial output continually reporting servo states while I fine tuned a robotic arm range of motion/interferences. Once I had it all set up good enough I disabled the serial output and the speed of execution more than quadrupled. I didn't realise it would have such an effect, at least on a lowly Uno, but if you run into speed issues in the future consider disabling/reducing serial outputs and see if that fixes your issue.
     
  20. Oct 8, 2018 #20

    Jester

    Jester

    Jester

    New Member

    Joined:
    Oct 7, 2018
    Messages:
    1
    Likes Received:
    0
    Gender:
    Male
    Location:
    Australia
    New to the forum, so be gentle on me...

    Are you able to change the ratio for the mill spindle speed to output speed on your a-axis?
    Say if your making a different tooth gear for example?
     

Share This Page