Höhenverstellung Frästisch

Umbau eines Frästischs auf elektronische Höhenverstellung (--> English)



Oberfräse: Casals (Freud) CT3000 VCE, siehe Woodworking-Forum
                    alternativ (Casals wird nicht mehr verkauft): Trend T11

Tisch: eigentlich egal, bei mir ein Incra-Frästisch, siehe Feinewerkzeuge


Intro

Die Oberfräse Casals CT3000 bietet zwar eine Höhenverstellung durch die Tischplatte hindurch, aufgrund der geringen Steigung der verbauten Spindel wird die Verstellung mit dem Inbusschlüssel jedoch schnell nervig. Wenn man Rechnen und Zählen der Umdrehungen vermeiden möchte, muss zudem regelmäßig durch Nachmessen das Erreichen der Zielhöhe kontrolliert werden.

Das haben auch andere Holzarbeiter schon festgestellt, und so gibt es einige Umbauten der Höhenverstellung auf Akkuschrauber (aber auch Bowdenzüge und Wagenheber). Es bleibt das Problem der zu überprüfenden Zielhöhe. Zudem führen die höheren Drehzahlen zum Verschweißen des manuellen Drehrades (Umbau gegen das Verschweißen).

Meine hier beschriebene Lösung soll keine Anleitung zum Nachbau sein, sondern beschreibt lediglich den von mir durchgeführten Um-/Aufbau als Anregung. Die Beschreibung hat keinen Anspruch auf Vollständigkeit oder Korrektheit. Ich übernehme keinerlei Haftung oder Verantwortung für evtl. entstehende Sach- oder Personenschäden. Da die von mir verwendete Methode sich deutlich von den anderen mir bekannten unterscheidet, habe ich mein Verfahren hier beschrieben - vielleicht hilft es als Denkanstoß.

Überblick

Ich habe an die Fräse zur Höhenverstellung einen Stepper-Motor angeschlossen. Der Motor dreht die Spindel pro Schritt um 1.8°, nach 200 Schritten hat der Motor eine Umdrehung geschafft. Bei einer Spindelsteigung von 1.5 mm bewegt sich der Fräskopf also exakt um 1.5/200 = 0.0075 mm pro Schritt (abgesehen vom System-Spiel, insbesondere der Spindelmutter). Braucht im Holzbau natürlich keiner, aber es ist reproduzierbar. Ich betreibe den Motor mit 400 Steps/s, das sind 3 mm/s Verfahrgeschwindigkeit.


Die umgebaute Fräse, auf der Aluplatte ist oben rechts der Stepper verschraubt


Der verbaute Stepper-Motor

Einmal auf die aktuelle Höhe des Fräskopfes kalibriert, kann dieser hochgenau und absolut reproduzierbar auf beliebige Höhen gefahren werden. Über den Drehknopf kann ein Zielwert eingedreht werden, der dann vom Fräskopf angefahren wird. Das System ist extrem schlank, der Einbau benötigt kaum Platz. Die Oberfräse kann nach Trennung der Stepper-Versorgung (4-poliger Molex) genauso einfach wie vor dem Umbau aus dem Tisch herausgenommen werden.

Das übliche Vorgehen beim Fräsen sieht mit meiner Installation so aus:

1. Fräskopf einbauen und Höhe messen

2. Ein 2-sekündiges Drücken des Set-Tasters aktiviert den SET-Modus. Über den Drehregler gebe ich die gemessene Höhe ein, der Fräser bewegt sich dabei nicht. Ein weiteres Drücken des Set-Tasters beendet den SET-Modus. Das System ist kalibriert.


3. Über den Drehregler lassen sich beliebige Höhen anfahren. Ein Drücken des Drehreglers schaltet das Drehinkrement zwischen 0.1 mm und 0.5 mm um.


Gegen versehentliche Eingaben (z.B. während des Fräsvorgangs) gibt es den LOCK-Modus, der sich durch Drücken des Drehreglers für mindestens 2 Sekunden aktivieren lässt. Eingaben sind nun nicht mehr möglich - außer das erneut 2-sekündige Drücken des Drehreglers zum Entsperren.

    Material

    Um den Drehwiderstand der Spindel zu reduzieren, habe ich
      • die Federn aus der Fräse entfernt, die das Gerät im Oberfräsenbetrieb vom Werkstück weghalten. Upside-down eingebaut übernimmt das die Schwerkraft.

        Da die Höhenverstellung nur die Maximalauslenkung limitiert, muss gegen ein ungewolltes Eintauchen des Fräskopfes vor dem Fräsen natürlich stets die Säule blockiert werden!
        • die Original-Spindel gegen eine Trapezgewindespindel getauscht. Dafür habe ich verwendet:
          • Trapezgewindespindel TR8x1,5 rechts DIN103 1 mtr. (~10€)
          • Trapezgewindemutter TR8x1,5 re RG7 rund 22x20 (~15€)
          • Wellenkupplung starr D18L25 6,35/6,35 mm (~6€) für den Anschluss des Steppers, die Trapezspindel habe ich entsprechend abgedreht
        Für die Ansteuerung des Steppers benutze ich
        • einen Arduino Mega2560 als kleinen Rechner (~12€),
        • ein Prototype Shield ProtoShield V3 für das Anlöten der Zuleitungen (~5€),
        • ein Dual VNH2SP30 Stepper Motor Driver Module 30A Monster Moto Shield für den Schrittmotor (~18€),
        • ein SainSmart IIC/I2C/TWI Serial 20x4 LCD 2004 Module als Anzeige (~10€),
        • einen KY-040 Rotary Encoder Module Brick Sensor für die Höheneingabe (~2€),
        • einen Vandalismustaster für die Modus-Umschaltung (~10€),
        • einen Stepper Motor (~20€),
        • zwei Endschalter ME8108 für eine sichere Begrenzung des Verfahrwegs (~15€),
        • ein USB-Netzteil für den Arduino (~5€) und
        • ein Netzeil MEAN WELL HLG-60H-15B für die Ansteuerung des Steppers (~35€)
        • keine Widerstände, keine Kondensatoren, keine Spulen, keine Dioden, ...
        Macht aufsummiert ~163€ für den Umbau. Dazu kommen noch einige Drähte, ein Gehäuse, ggf. Lüsterklemmen, ganz wenig Lötzinn, Schuko-Kupplung für das Netzteil, viel Zeit und interessierte Kollegen, die mich elektrotechnisch beraten haben.

        Der Einbau im Tisch (ohne Fräse): über zwei Winkel gehen die Endschalter an die Aluplatte der Fräse. Diese lassen sich in der T-Nut des Tisches frei vertikal justieren. Hinter den Winkeln sitzt das Netzteil, darüber die USB-Versorgung

        Wie funktioniert es?


        Der Arduino, das Prototype Shield und das Stepper Shield lassen sich einfach aufeinanderstecken. Kein Löten, kein Schrauben, hält so. Am Arduino habe ich nichts aufgelötet und geändert. Der wird später im Gehäuse am Boden festgeschraubt.

        Der Arduino sitzt am Gehäuseboden, verkabelt nur über den USB-Anschluss


        Das Stepper Shield erhält am Eingang Strom vom MEAN WELL Netzteil. Die vier Ausgangsleitungen am Stepper-Shield gehen an den Schrittmotor, jeweils paarweise an die beiden Spulen. Den Stepper betreibe ich mit 15V und 1.8A. Das Netzteil liefert eigentlich bis 4A, ist aber regelbar über einen PWM-Eingang. Die 4A werden bei 10V am PWM-Eingang geliefert. Über den Arduino erzeuge ich ein PWM-Signal mit 4.5V (5V-Signal mit 90% Tastverhältnis), dann begrenzt das Netzteil den Strom auf die gewünschten 1.8A. Interessant ist hier, dass ich den Strom vom Netzteil beliebig im Bereich 0-2A dynamisch begrenzen kann.

        Das Stepper-Shield in rot. Links (schwarz/rot) die Stromzufuhr vom Netzteil, die beiden braunen Stecker rechts gehen zum Motor, die Steuersignale kommen von den durchgeführten Pins

        Da der Arduino nur 5V Spannung hat, kann ich den Bereich 2-4A ohne Weiteres nicht ansteuern, brauche ich aber auch nicht. Das Netzteil gibt es auch in anderen Leistungsstufen, für mich war 2A bei 5V Steuerspannung aber ideal.

        Alle anderen elektrischen Verbindungen sind über das Prototype Shield verkabelt.
        • Der Vandalismustaster und die beiden Endabschalter schalten jeweils GND auf ein Pin des Arduino (siehe Quellcode). Das Pin wird jeweils vorher mit einem internen Pull-Up auf 5V gezogen ("Low-aktiv").
          Die Endabschalter haben bauartbedingt eine große Hysterese. D.h., sobald der Schalter schließt muss die Fräse um mehrere Millimeter zurückgefahren werden, um den Schalter wieder zu öffnen. Sollte beim Einschalten der Fräse einer der Schalter geschlossen sein, muss das folglich nicht an einer unerlaubten Position der Höhenverstellung liegen, sondern wird eventuell von der Hysterese verursacht. Deshalb versucht das Programm beim Einschalten im INIT-Modus die Schalter freizufahren und nimmt erst dann den regulären Betrieb auf.
        • Der Dreh-Encoder ist ganz ähnlich angeschlossen, einen Pull-Up hat das Modul bereits eingebaut. Der Encoder hat 5 Anschlüsse: GND, VCC (5V vom Arduino für den Pull-Up, unbedingt mit anschließen oder Pull-Ups auslöten!), zwei Pins kodieren die Drehrichtung und der Drehknopf hat noch einen Drucktaster. Beim dekodieren der Drehrichtung hilft die Arduino-Library "Encoder".
        • Das LCD-Modul hat 4 Anschlüsse: GND, VCC, und 2 Pins für einen I2C-Bus. Für die Ansteuerung gibt es die "LiquidCrystal_I2C"-Library. Man sollte keinesfalls zu viel oder zu hochfrequent auf das Display schreiben. Zum einen sind "setCursor" und "print" sehr langsam und stören das runde Laufen des Motors, außerdem sehen schnelle Änderungen auf dem Display auch einfach nicht gut aus. Mehr als 2-3 Hz macht eigentlich keinen Sinn.

           

        Rechts das Proto-Shield auf dem Arduino-Board mit Verkabelung, links im Deckel oben das Display, darunter Taster und Inkrementgeber

        Eine weitere sinnvolle Library für das Projekt heißt "AccelStepper". Diese erleichtert die Ansteuerung des Steppers erheblich. Nach der Definition von Maximalgeschwindigkeit und Beschleunigung (es werden Beschleunigungs- und Verzögerungsrampen berechnet) können Zielwerte sehr einfach angefahren werden.

        Programmiert werden kann das Ganze mit der Arduino-IDE Software. Einfach
        • das Arduino-Board über USB an den PC hängen, 
        • notwendige Treiber automatisch installieren lassen, 
        • den Quellcode öffnen und 
        • über die beiden Schaltflächen oben links erst kompilieren und 
        • dann auf das Board übertragen. 
        • Fehlende Libraries können komfortabel über "Sketch/Bibliothek einbinden/Bibliothek verwalten" gesucht und installiert werden.
        Dann braucht man nur noch wenige Zeilen Quellcode, meiner ist zugegebenermaßen eher funktional und prototypisch als schön 😅:

        #include <Wire.h>
        #include <LiquidCrystal_I2C.h>
        #include <AccelStepper.h>
        #include <Encoder.h>
        #define DEFAULT_ALLOW 20000

        AccelStepper stepper(AccelStepper::FULL4WIRE,7,8,4,9);  // INA1, INB1, INA2, INB2
        bool initialized=false;
        int lastInc=-1;
        int lastSwitch = 0;
        int height=0;
        int factor=5;
        int factorPrint=0;
        bool stepperDisabled=false;
        bool lowerReached=false;
        bool upperReached=false;
        const int pwmA = 5;
        const int pwmB = 6;
        const int inc1 = 18;
        const int inc2 = 19;
        Encoder enc(inc2,inc1);
        const int incSwitch = 37;
        const int lowerBound=38;
        const int upperBound=39;
        const int setSwitch=47;
        const int currentSet=44;
        const int currentSwitch[2]={A2,A3};   // A2 and A3 to get current from motor shield
        int lastHeight=-1;
        int posOffset=0;
        int minAllowed=-DEFAULT_ALLOW;
        int maxAllowed=+DEFAULT_ALLOW;
        int current=0;
        int currentPrint=-1;
        bool locked = false;
        bool setValueMode=false;
        bool setSwitchMode = false;
        long lastEncoder=0;
        long setSwitchStartTime = 0;
        long lastPrint = 0;
        long lastCurrentPrint = 0;
        int lastPowerCurrent=9999;    // Force print
        long lastIncTime = 0;
        const float conversion=20/1.5;
        LiquidCrystal_I2C lcd(0x3f,20,4);  // set the LCD address to 0x3f for a 20 chars and 4 line display
        void setup()
        {
          pinMode(incSwitch, INPUT);
          digitalWrite(incSwitch, HIGH);
          pinMode(lowerBound, INPUT);
          digitalWrite(lowerBound, HIGH);
          pinMode(upperBound, INPUT);
          digitalWrite(upperBound, HIGH);
          pinMode(setSwitch, INPUT);
          digitalWrite(setSwitch, HIGH);
          pinMode(currentSwitch[0], INPUT);
          pinMode(currentSwitch[1], INPUT);
          pinMode(pwmA, OUTPUT);
          pinMode(pwmB, OUTPUT);
          pinMode(currentSet, OUTPUT);
          setPowerSupply(1.8);

          digitalWrite(pwmA, HIGH);
          digitalWrite(pwmB, HIGH);

          stepper.setMaxSpeed(400);

          stepper.setAcceleration(600);

          lcd.init();                      // initialize the lcd
          lcd.init();
          // Print a message to the LCD.
          lcd.backlight();
          lcd.setCursor(0,0);
          lcd.print("Router Control:");
          setMode("INIT");
          Serial.begin(115200);      // open the serial port at 115200 bps:

        }
        void setPowerSupply(float amps)
        {
          analogWrite(currentSet, amps*255/2.);    // power supply delivers 4A@10V, 2A@5V
        }
        void setMode(const char *a)
        {
            lcd.setCursor(16,0);
            lcd.print(a);
        }
        void printLCD(int value, bool small=false)
        {
          char buffer[16];
          value+=posOffset;
          if (value>=0) buffer[0]='+';
          else buffer[0]='-';
          if (small)
            sprintf(&buffer[1],"%4i.%1i",abs(value)/10,abs(value)%10);
          else
            sprintf(&buffer[1],"%4i.%1imm",abs(value)/10,abs(value)%10);
          lcd.print(buffer);
          
        }
        void loop(){
          unsigned c[2];
          c[0]=analogRead(currentSwitch[0]);
          c[1]=analogRead(currentSwitch[1]);
          int powerCurrent = 300.*((c[0]>c[1])? c[0]:c[1])/811.;
          long newPosition=lastEncoder;
          long currentTime=millis();

          if (initialized)
            newPosition = enc.read();
          if (newPosition!=lastEncoder && (abs(newPosition))%4==0)
          {
            long diff=(newPosition-lastEncoder);
            lastEncoder = newPosition;
            if (locked)
            {
              ;
            }
            else if (setValueMode)
            {
              posOffset+=diff*factor/5;
              lcd.setCursor(0,2);
              printLCD(current);
              lcd.setCursor(0,3);
              printLCD(height);
              if (minAllowed!=-DEFAULT_ALLOW)
              {
                lcd.setCursor(0,1);
                printLCD(minAllowed, true);
              }
              if (maxAllowed!=DEFAULT_ALLOW)
              {
                lcd.setCursor(8,1);
                printLCD(maxAllowed, true);
              }
            }
            else
              height+=diff*factor;
          }
          lastEncoder=newPosition;
          int val1,val2;
          if (stepper.distanceToGo() == 0) {
            stepper.disableOutputs();
            stepperDisabled=true;
          //  setPowerSupply(0.2);
            stepper.run();                   // let the AccelStepper disable motor current after stop
          }
          stepper.run();
          val1 = !digitalRead(incSwitch);
          if (val1 || val1!=lastSwitch)       // inc button pressed!
          {
            if (!lastSwitch && val1)    // just pressed, store time
              lastIncTime = currentTime;
            if ((currentTime-lastIncTime>2000) && val1)
            {
              locked = !locked;
              if (locked) setMode("LOCK");
              else setMode("WORK");
              lastIncTime=currentTime+50000;      // avoid toggeling
            }
            else if (!locked && lastSwitch && !val1)   // just released
            {
              factor = (factor==5)? 50:5;
            }
            lastSwitch=val1;
          }
          if ((currentTime-lastCurrentPrint)>500 && lastPowerCurrent != powerCurrent)
          {
            Serial.println(powerCurrent);
              char buffer[8];
              lcd.setCursor(14,2);
              sprintf(buffer,"%2i.%1iA",powerCurrent/10,abs(powerCurrent)%10);
              lcd.print(buffer);
              lastPowerCurrent=powerCurrent;
              lastCurrentPrint = currentTime;
          }
          if (factorPrint!=factor)
          {
              lcd.setCursor(15,3);
              if (factor==5) lcd.print("0.5");
              else lcd.print("5.0");
              factorPrint=factor;
          }
          bool old=lowerReached;
          lowerReached=!digitalRead(lowerBound);
          if (old!=lowerReached || lowerReached)
          {
            if (lowerReached && stepper.distanceToGo()<0 && minAllowed==-DEFAULT_ALLOW)
            {
              minAllowed=current;
              lcd.setCursor(0,1);
              printLCD(minAllowed,true);
            }
          }
          old = upperReached;
          upperReached=!digitalRead(upperBound);
          if (old!=upperReached || upperReached)
          {
            if (upperReached && stepper.distanceToGo()>0 && maxAllowed==DEFAULT_ALLOW)
            {
              maxAllowed=current;
              lcd.setCursor(8,1);
              printLCD(maxAllowed, true);
            }
          }
          old = setSwitchMode;
          setSwitchMode=!digitalRead(setSwitch);
            if (!locked && setSwitchMode)                                  // pressed
            {
              if (old!=setSwitchMode)                           // just pressed
              {
                  setSwitchStartTime = currentTime;
                  if (setValueMode)
                  {
                    setValueMode=false;setMode("WORK");
                  }
              }
              else if (currentTime-setSwitchStartTime>2000 && !setValueMode)        // pressed for 2 seconds
              {
                  setValueMode=true;setMode("SET!");
              }
            }
          if (height>maxAllowed) height=maxAllowed;
          if (height<minAllowed) height=minAllowed;
          if (lastHeight!=height)
          {
            if (stepper.targetPosition()!=height*conversion)
            {
                 if (stepperDisabled)
                 {
                    stepper.enableOutputs();
                  
                    //setPowerSupply(1.8);
                    stepper.run();
                  
                    stepperDisabled=false;
                    delay(50);
                 }
                 stepper.moveTo(height*conversion);
                 stepper.run();
            }
        //    Serial.print("----------------");
        //    Serial.println(height);
            lcd.setCursor(0,3);
            printLCD(height);
            lastHeight=height;
          }
          if (stepper.distanceToGo() != 0)
          {
            current=round(stepper.currentPosition()/conversion);
          }
          if (currentPrint!=current && abs(currentTime-lastPrint)>300)    // print current pos with no more than 3 Hz
          {
            lcd.setCursor(0,2);
            printLCD(current);
            currentPrint = current;
            lastPrint = currentTime;
          }
          if (!initialized)
          {
            if (lowerReached == upperReached)
            {
              setMode("WORK");
              minAllowed= -DEFAULT_ALLOW;
              maxAllowed= +DEFAULT_ALLOW;
              initialized=true;
            }
            else if (fabs(stepper.distanceToGo())<25*conversion)
            {
              if (lowerReached)
                height+=50;
              else
                height-=50;
            }
          }
        }

        Auf der Könnte-man-noch-machen-Liste steht noch:
        • Zur Zeit wird die Fräshöhe immer direkt angefahren. Aufgrund des Spiels im System ist es ein Unterschied, ob eine Höhe von oben oder unten kommend erreicht wird, von etwa 0.2 mm. Das ist nicht wirklich ein Show-Stopper, ließe sich aber vermeiden, wenn die Zielhöhe immer von unten angefahren würde. Sprich: wenn es bergab geht ruhig ein wenig überschießen und dann wieder hochfahren. Das dürfte nur eine kleine Änderung in der Software sein.
        • Das Display (LCD 2004) ist eigentlich eine Krücke. Ineffizient anzusteuern, langsame Updates, schlecht abzulesen - da könnte man auch mal was moderneres einbauen.
        • Da ich viel mit Bündigfräsern arbeite, und die bei ungünstiger Holzfaserlage gerne ins Holz hacken, drehe ich ich das Werkstück häufig um, so dass die Vorlage mal auf und mal unter dem Werkstück liegt. Dafür habe ich einen langen Bündigfräser mit oberen und unteren Anlaufring. Die Fräshöhe muss dann immer zwischen den beiden Anlaufringhöhen hin- und hergeschaltet werden - hierfür wären Preset-Tasten, ähnlich den Speichertasten eines Radios, sehr sinnvoll!
        • Statt einer manuellen Kalibrierung könnte man das auch automatisch machen (eine INIT-Phase zum Freilaufen der Endschalter gibt es ja eh schon). 
          • Eine Möglichkeit wäre eine Lichtschranke knapp unter dem Tisch (anfällig auf Sägestaub? Platzproblem mit Absaugung).
          • Auch eine Metallplatte, die über die Fräseraussparung gelegt wird und vom Fräskopf beim Gegenfahren auf GND gezogen wird wäre eine denkbare Lösung. 
          • Allerdings wäre das nur bei einigen Fräsköpfen sinnvoll. Bei einem Fräser mit Anlaufring spielt die Spitze keine Rolle, man würde wohl eher auf die mittlere Höhe des Anlaufrings als Referenz kalibrieren.
            Bei einem V-Nut Fräser wäre (zumindest bei der Lichtschranke) die korrekte Lage der Messstrecke sehr wichtig.
        • Das größte Ärgernis ist die Blockierung der Säule, für das ich auch die wenigsten Ideen habe. Insbesondere für Spiralnutfräser ist das Blockieren zwingend notwendig.
          • Ein Traum wäre natürlich, wenn der Arduino die Klemmung gleich mit steuern könnte, also eine motorbetriebene/magnetische/hydraulische/WasAuchImmer Klemmung. Da ich aber an der Fräse selbst nicht zuviel basteln möchte, und auch das Herausnehmen der Oberfräse aus dem Tisch noch gut möglich sein soll, schreckt mich das ab. Statt die Originalklemmung zu verändern, würde ich wohl eher an der Aluplatte eine weitere Stange befestigen, die dann z.B. über eine federbetätigte Feststelleinheit gebremst wird, sieht allerdings teuer aus.
          • Einfacher wäre wohl ein Sensor für die Klemmung der Säule. Dann könnte man zumindest über eine LED den Blockierstatus leicht sehen - und dem Arduino verbieten, bei blockierter Säule den Motor anzusteuern. Umgekehrt könnte man über ein Relais das Anlaufen der Fräse verbieten, wenn nicht blockiert ist. Auch diese Lösung erfordert einen An-/Umbau.
          • Eine dritte Möglichkeit ist der Umbau der Höhenverstellung von Maximalauslenkung auf exakte Auslenkung, also eine beidseitige MinMax-Beschränkung der Höhe.  Der mechanische Umbau der Fräse auf Trapezgewindespindel ist schon ein bisschen her, keine Ahnung, ob das so einfach möglich wäre. Da die Originalspindel auch nur einseitig beschränkt, ist es wahrscheinlich nicht so einfach. Zudem würde natürlich der Drehwiderstand der Spindel steigen. Pro ist noch zu vermerken, dass durch diese Methode (wenn man die Klemmung weglässt) auch ins Werkstück bei drehendem Fräser tauchen könnte. Die beidseitige Führung ließe sich auch durch eine zweite, externe Spindel über einen eigenen Schrittmotor erreichen, die z.B. an der Aluplatte ansetzt - bliebe das Syncen der Motoren.
          • Ein Wiedereinbau der Säulenfedern verringert sicherlich das Problem. Ich habe noch nicht getestet, ob der Motor das mitmacht, denke aber schon. Eine verlässliche Methode gegen das unabsichtliche Eintauchen ist aber auch das nicht - es wird nur unwahrscheinlicher. Beim Runterfahren des Fräskopfes macht der Motor (aufgrund des fehlenden Drucks?) schrille Geräusche, hier könnte die Feder auch helfen.
        Was meint ihr? Ich freue mich über Kommentare, Lob & Tadel und vor allem Verbesserungsvorschläge.

        Kommentare

        Beliebte Posts aus diesem Blog

        Eckschreibtisch für Jugendzimmer

        Die Beine der Festool CS70 - Umbau auf Rollcontainer