Automatik-Vorschub für Drehbank

Hi

Bin momentan dabei, meiner Drehbank einen automatischen Vorschub zu verpassen.
Inspiriert durch dieses YT-Video, nach gebaut mit ALPS-Encoder (15 Rasten a 4 Impulse), als Stepper einen 28BYJ-48 (4096 Steps, etwas schwachbrüstig) befeuert durch die MoBaTools vom microbahner hier aus dem Forum.
Die Aufnahme der Encoder-Signale geschieht per Interrupt (beide Signale mit der gleichen Routine).
Hoffe, ich habe nicht zu sehr mit Kommentaren gegeizt und vll. kann sogar Wer Was Damit anfangen.
Ist Momentan vll. eine Beta, die Warnung zur Zeile 130 kann ignoriert werden, der Wert wird bei Start global auf 0 gesetzt und ist somit definitiv vor der ersten Verwendung bekannt.

/*
  Nachbau von 'Lathe Electronic Leadscrew'
  https://www.youtube.com/watch?v=FTs9GygRQ-U

  
  Verwendeter Derehencoder von ALPS, liefert 4 Impulse pro Rastung, 15 Rastungen pro Umdrehung
  =60 Impulse pro Umdrehung.
  Die verwendeten MoBaTools nehmen die Zielposition des Stepper als Winkel entgegen (unter Anderem).
  Bei Faktor=6 und Teiler=1 entspricht die Encoder-Umdrehung der Stepper-Umdrehung (60x6=360)
  Bei Faktor=90 und Teiler=1 entspricht jede Rastung einer Umdrehung (4 Impulse x 90 = 360°)
  Faktor=60 und Teiler=10 entspricht Faktor=6 und Teiler=1, man kann so auf 10.tel Grad runter
  Umschalten zwischen den Einzelwerten durch Button-Betätigung, per Software entprellt.
  Aufnahme der Encoder-Phasen per Interrupt (beide Signale in der gleichen Routiene).
  Davon verspreche ich mir, keinen Wechsel zu verpassen - als Encoder ist ein anderes Modell
  mit meeehhhrrrr Impulsen pro Umdrehung angedacht (und ohne Rasten).
  Allerdings ist ein Modell mit 4096 Impulsen/Umdrehung für einen Versuch doch etwas teuer - es
  wird daher vorerst ein Modell mit 400 Impulsen/Umdrehung.
  Wie in dem ver-url-tem Video soll der Kram irgend wann Mal an meine Drehbank und schnieke
  Gewinde oder allgemein einstellbare Vorschübe ermöglichen, was bisher nur durch Tausch der Wechselräder
  funktioniert.
  ... to be continue ...
  
  V1_1
  Teiler und Faktor sind per Encoder einstellbar
  Berechnung auf int64_t gesetzt, jetzt klappt's auch mit den Faktoren
  Bei Button-Betätigung werden Winkel, Faktor und Teiler ausgegeben, wie
  der aktive Part per * angezeigt, somit ist das 'Übersetzungsverhältnis'
  so weit integriert. Adaption auf anderen Stepper/Treiber fehlt, MoBaTools soll bleiben
*/

const byte pin_phase_a = 2;   //Phase A des Encoder
const byte pin_phase_b = 3;   //Phase B des Encoder
const byte pin_button = 5;    //Button des Encoder, Umschalten zwischen Wert und Faktor
volatile uint32_t counter = 0;         //Zählwert des Encoder
volatile boolean change = false;
uint32_t counteroffset = 0;   //Offset, ab wann der aktuelle Faktor gilt
uint32_t winkeloffset = 0;    //Offset des Winkel zum Counteroffset
int32_t winkelwert = 0;    //berechneter Winkel zum Counteroffset
int32_t faktor = 6;         //Faktor, mit Dem der Counter in den Winkel eingeht -> Encoderumdrehung=Stepeprumdrehung
uint32_t teiler = 1;   //Teiler zum Faktor, um den Faktor z.B. in 100.stel anzunehmen
byte wtc = 0;               //'what to change' 0=Winkel, 1=Faktor, 2=Teiler einstellen
uint32_t lastbutton = 0;      //millis()-Wert der letzten Button-Änderung
const boolean pressed = LOW; //Pinzustand bei gedrücktem Button
boolean buttonpressed = !pressed; //letzte Zustand des Button
const uint32_t debouncetime = 20; //Entprellzeit in ms

#include <MobaTools.h>
Stepper4 Step1(4096);           // HALFSTEP ist default, Anzahl an (Halb)Schritten für volle Umdrehung
                                // Es soll auch 28BYJ-48 geben mit krummen Übersetzungsverhältnis!
                                // Meine sind aber Alle 4096 Step/Umdrehung (gemessen/gezählt)
//Konstanten für Stepper
const byte maxspeed = 150;      //Umdrehungen x10 pro Minute
const uint16_t  maxacc = 5;     //Je weniger, desto schneller Beschleunigung

void setup() {
  Serial.begin(115200);
  pinMode(pin_phase_a, INPUT_PULLUP);
  pinMode(pin_phase_b, INPUT_PULLUP);
  pinMode(pin_button, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(pin_phase_a), ISR_phase, CHANGE);
  attachInterrupt(digitalPinToInterrupt(pin_phase_b), ISR_phase, CHANGE);
  Step1.attach( 9, 8, 7, 6 ); // Anschluß an digitalen Ausgängen; Treiber IN1,IN2,IN3,IN4
  Step1.setSpeed( maxspeed );        // = 24 U/Min
  Step1.setRampLen(maxacc);        // Beschleunigung
  Step1.setZero();              // Referenzpunkt f�r Motor 1 setzen
}

void loop() {
  // put your main code here, to run repeatedly:
  static uint32_t counterwert;
  if (change) {
    //Es wurde ein Interrupt ausgeführt
    do {
      change = false; //Flag löschen
      counterwert = counter;  //Wert auf Variable übergeben
    } while (change == true); //wenn während Dessen die ISR aktiv war, erneut einlesen
    uint32_t abstand = 0;
    if (counterwert > counteroffset) {
      abstand = counterwert - counteroffset;
    } else {
      abstand = counteroffset - counterwert;
    }
    switch (wtc) {
      case 0://Winkel soll am Counterwert hängen
        winkelwert = -int64_t(counterwert - counteroffset) * faktor / teiler + winkeloffset;
        Step1.write(winkelwert); //counterwert 60 pro Umdrehung, Ziel-Winkel an Stepper
        Serial.print("Zielwinkel:");
        Serial.println(winkelwert);
        break;
      case 1://Faktor soll am Counterwet hängen
        //counter, counteroffset, faktor
        if (abstand >= 4) {
          faktor = faktor + int32_t(counterwert - counteroffset) / 4;
          counteroffset = counterwert;
        }
        Serial.print("Faktor:");
        Serial.println(faktor);
        break;
      case 2://Teiler soll am Counterwert hängen
        //counter, counteroffset, faktor
        if (abstand >= 4) {
          if (-int32_t(counterwert - counteroffset) / 4 >= int32_t(teiler)) {
            teiler = 1;
          } else {
            teiler = teiler + int32_t(counterwert - counteroffset) / 4;
          }
          counteroffset = counterwert;
        }
        Serial.print("Teiler:");
        Serial.println(teiler);
        break;
      default://zurück auf Anfang
        wtc = 0;
        break;
    }
  }
  byte isbuttonpressed = digitalRead(pin_button);
  if (isbuttonpressed != buttonpressed) {
    if (millis() - lastbutton >= debouncetime) {
      lastbutton = millis();
      if (isbuttonpressed == pressed) {
        //Button ist gedrückt, kommt beim ersten Mal durch, dann für debouncetime nicht mehr
        wtc = ++wtc % 3;
        if (wtc==0) Serial.print('*');
        Serial.print("Winkel\t");
        if (wtc==1) Serial.print('*');
        Serial.print("Faktor\t");
        if (wtc==2) Serial.print('*');
        Serial.print("Teiler\n");
        Serial.print(winkelwert);
        Serial.print('\t');
        Serial.print(faktor);
        Serial.print('\t');
        Serial.println(teiler);
        counteroffset = counterwert; //Offset neu setzen
        winkeloffset = winkelwert;  //Offset nur für Winkel relevant, müsste aber in 'Teiler' stehen und ändert sich sonst nicht
      }
    }
    buttonpressed = isbuttonpressed; //neuen Zustand übernehmen
  }

  /*
   // Herzschlag
    static boolean printflag = false;
    if (millis() - lastmillis >= 1000) {
      lastmillis += 1000;
      if (!printflag) { //nur 1x alle Sekunde ausgeben
        Serial.print('+');
        Serial.println(counter);
        printflag = true;
      }
    } else {
      printflag = false;
    }
  */
}

/*
  //bei oberem Bit==1 untere Bit umdrehen, so bekommen wir eine zählbare Reihenfolge zum Encoder-Bit-Muster
  //PLUS        //MINUS
  11  (10)      11  (10)
  10  (11)      01  (01)
  00  (00)      00  (00)
  01  (01)      10  (11)
*/

void ISR_phase(void) {
  //untere Bit
  byte static old_wert = 2; //keine Phase aktiv, Beide per PullUP hochgezogen, untere Bit umgedreht
  byte new_wert = digitalRead(pin_phase_b) << 1 | digitalRead(pin_phase_a);
  if (new_wert & 0x02) new_wert ^= 0x01; //wenn oberes Bit gesetzt ist, das Untere umdrehen
  if (new_wert == ((old_wert + 1) & 0x03)) counter++;
  if (old_wert == ((new_wert + 1) & 0x03)) counter--;
  old_wert = new_wert;
  change = true;  //Änderung in der loop() anzeigen
}

Jetzt fehlt 'nur noch' das Aufpumpen auf einen ordentlichen Stepper und der andere Encoder.
... neben dem mechanischem Umbau der Drehe ... Was wohl, wie ich mich kenne, noch etwas dauern wird ...

MfG
Edit Arduino-NANO

#ALPS, #Encoder, #LeadScrew, #Längsvorschub, #Drehbank, #Lathe, #Stepper, #MoBaTools

Haben wir für Dich noch nicht oft genug geschrieben einen mechanischen (Tastaturersatz) Encoder nicht über Interrupt auszulesen?

Grüße Uwe

Hehe

Ne :slight_smile:
Erhoffe mir von dem anderen Encoder bessere Qualität (wobei ALPS durchaus ein Name ist) und mit dem verwendeten Encoder mag Er zwar tierisch oft hin und her zählen, aber bis jetzt zählt Er zumindest korrekt.

Mir ist das Problem durchaus bewusst, befürchte aber, durch Pollen bei feinerem Encoder Schritte zu übersehen.
Ob dieser Weg gangbar ist- wird sich zeigen müssen, aber ich hoffe, daß mir das RAM dieses dutzende Male überschreiben nicht übel nehmen wird.
Sollte ich zeitliche Probleme bekommen, kann ich immerhin noch auf direkte Port-Zugriffe runter gehen um die ISR schneller wieder zu verlassen.

Danke Dir für Deine Kritik, hoffe aber, hier 'was Anderes' zu machen - bin eigentlich ganz nah bei Dir!

MfG

ohne ISR, in loop() wird die ehemalige Funktion gepollt, leicht umgeschrieben

/*
  Nachbau von 'Lathe Electronic Leadscrew'
  https://www.youtube.com/watch?v=FTs9GygRQ-U


  Verwendeter Derehencoder von ALPS, liefert 4 Impulse pro Rastung, 15 Rastungen pro Umdrehung
  =60 Impulse pro Umdrehung.
  Die verwendeten MoBaTools nehmen die Zielposition des Stepper als Winkel entgegen (unter Anderem).
  Bei Faktor=6 und Teiler=1 entspricht die Encoder-Umdrehung der Stepper-Umdrehung (60x6=360)
  Bei Faktor=90 und Teiler=1 entspricht jede Rastung einer Umdrehung (4 Impulse x 90 = 360°)
  Faktor=60 und Teiler=10 entspricht Faktor=6 und Teiler=1, man kann so auf 10.tel Grad runter
  Umschalten zwischen den Einzelwerten durch Button-Betätigung, per Software entprellt.
  Aufnahme der Encoder-Phasen per Interrupt (beide Signale in der gleichen Routiene).
  Davon verspreche ich mir, keinen Wechsel zu verpassen - als Encoder ist ein anderes Modell
  mit meeehhhrrrr Impulsen pro Umdrehung angedacht (und ohne Rasten).
  Allerdings ist ein Modell mit 4096 Impulsen/Umdrehung für einen Versuch doch etwas teuer - es
  wird daher vorerst ein Modell mit 400 Impulsen/Umdrehung.
  Wie in dem ver-url-tem Video soll der Kram irgend wann Mal an meine Drehbank und schnieke
  Gewinde oder allgemein einstellbare Vorschübe ermöglichen, was bisher nur durch Tausch der Wechselräder
  funktioniert.
  ... to be continue ...

  V1_2
  wie V1_1, nur pollen der Encoder-Signale - wird in jedem loop()-Durchlauf aufgerufen

  V1_1
  Teiler und Faktor sind per Encoder einstellbar
  Berechnung auf int64_t gesetzt, jetzt klappt's auch mit den Faktoren
  Bei Button-Betätigung werden Winkel, Faktor und Teiler ausgegeben, wie
  der aktive Part per * angezeigt, somit ist das 'Übersetzungsverhältnis'
  so weit integriert. Adaption auf anderen Stepper/Treiber fehlt, MoBaTools soll bleiben

  V1_0
  Sketch fragt per Interrupt einen Encoder ab, je nach Drehrichtung wird 'counter' entsprechend hoch/runter gezählt
  Bei dem verwendeten Alps-Encoder mit 15 Rasten und 4 Signalwechseln pro Raste = pro Umdrehung 60 Increments
  Diesen Wert Mal 60 = Zielwinkel für Stepper (28BYJ-48, 4096 Halb-Step pro Umdrehung)
  Null-Setzen des Stepper fehlt noch, auch das 'Übersetzungsverhältnis' zwischen Encoder und Stepper

*/

const byte pin_phase_a = 2;   //Phase A des Encoder
const byte pin_phase_b = 3;   //Phase B des Encoder
const byte pin_button = 5;    //Button des Encoder, Umschalten zwischen Wert und Faktor
volatile uint32_t counter = 0;         //Zählwert des Encoder
volatile boolean change = false;
uint32_t counteroffset = 0;   //Offset, ab wann der aktuelle Faktor gilt
uint32_t winkeloffset = 0;    //Offset des Winkel zum Counteroffset
int32_t winkelwert = 0;    //berechneter Winkel zum Counteroffset
int32_t faktor = 6;         //Faktor, mit Dem der Counter in den Winkel eingeht -> Encoderumdrehung=Stepeprumdrehung
uint32_t teiler = 1;   //Teiler zum Faktor, um den Faktor z.B. in 100.stel anzunehmen
byte wtc = 0;               //'what to change' 0=Winkel, 1=Faktor, 2=Teiler einstellen
uint32_t lastbutton = 0;      //millis()-Wert der letzten Button-Änderung
const boolean pressed = LOW; //Pinzustand bei gedrücktem Button
boolean buttonpressed = !pressed; //letzte Zustand des Button
const uint32_t debouncetime = 20; //Entprellzeit in ms

#include <MobaTools.h>
Stepper4 Step1(4096);           // HALFSTEP ist default, Anzahl an (Halb)Schritten für volle Umdrehung
// Es soll auch 28BYJ-48 geben mit krummen Übersetzungsverhältnis!
// Meine sind aber Alle 4096 Step/Umdrehung (gemessen/gezählt)
//Konstanten für Stepper
const byte maxspeed = 150;      //Umdrehungen x10 pro Minute
const uint16_t  maxacc = 5;     //Je weniger, desto schneller Beschleunigung

void setup() {
  Serial.begin(115200);
  pinMode(pin_phase_a, INPUT_PULLUP);
  pinMode(pin_phase_b, INPUT_PULLUP);
  pinMode(pin_button, INPUT_PULLUP);
  Step1.attach( 9, 8, 7, 6 ); // Anschluß an digitalen Ausgängen; Treiber IN1,IN2,IN3,IN4
  Step1.setSpeed( maxspeed );        // = 24 U/Min
  Step1.setRampLen(maxacc);        // Beschleunigung
  Step1.setZero();              // Referenzpunkt f�r Motor 1 setzen
}

void loop() {
  ISR_phase();  //Encoder abfragen, bei Verstellen 'counter' mitzählen

  static uint32_t counterwert;
  if (change) {
    //Es wurde eine Verdrehung des Encoder festgestellt
    change = false; //Flag löschen
    counterwert = counter;  //Wert auf Variable übergeben
    uint32_t abstand = 0;
    if (counterwert > counteroffset) {
      abstand = counterwert - counteroffset;
    } else {
      abstand = counteroffset - counterwert;
    }
    switch (wtc) {
      case 0://Winkel soll am Counterwert hängen
        winkelwert = -int64_t(counterwert - counteroffset) * faktor / teiler + winkeloffset;
        Step1.write(winkelwert); //counterwert 60 pro Umdrehung, Ziel-Winkel an Stepper
        Serial.print("Zielwinkel:");
        Serial.println(winkelwert);
        break;
      case 1://Faktor soll am Counterwet hängen
        //counter, counteroffset, faktor
        if (abstand >= 4) {
          faktor = faktor + int32_t(counterwert - counteroffset) / 4;
          counteroffset = counterwert;
        }
        Serial.print("Faktor:");
        Serial.println(faktor);
        break;
      case 2://Teiler soll am Counterwert hängen
        //counter, counteroffset, faktor
        if (abstand >= 4) {
          if (-int32_t(counterwert - counteroffset) / 4 >= int32_t(teiler)) {
            teiler = 1;
          } else {
            teiler = teiler + int32_t(counterwert - counteroffset) / 4;
          }
          counteroffset = counterwert;
        }
        Serial.print("Teiler:");
        Serial.println(teiler);
        break;
      default://zurück auf Anfang
        wtc = 0;
        break;
    }
  }
  byte isbuttonpressed = digitalRead(pin_button);
  if (isbuttonpressed != buttonpressed) {
    if (millis() - lastbutton >= debouncetime) {
      lastbutton = millis();
      if (isbuttonpressed == pressed) {
        //Button ist gedrückt, kommt beim ersten Mal durch, dann für debouncetime nicht mehr
        wtc = ++wtc % 3;
        if (wtc == 0) Serial.print('*');
        Serial.print("Winkel\t");
        if (wtc == 1) Serial.print('*');
        Serial.print("Faktor\t");
        if (wtc == 2) Serial.print('*');
        Serial.print("Teiler\n");
        Serial.print(winkelwert);
        Serial.print('\t');
        Serial.print(faktor);
        Serial.print('\t');
        Serial.println(teiler);
        counteroffset = counterwert; //Offset neu setzen
        winkeloffset = winkelwert;  //Offset nur für Winkel relevant, müsste aber in 'Teiler' stehen und ändert sich sonst nicht
      }
    }
    buttonpressed = isbuttonpressed; //neuen Zustand übernehmen
  }

  /*
    // Herzschlag
    static boolean printflag = false;
    if (millis() - lastmillis >= 1000) {
      lastmillis += 1000;
      if (!printflag) { //nur 1x alle Sekunde ausgeben
        Serial.print('+');
        Serial.println(counter);
        printflag = true;
      }
    } else {
      printflag = false;
    }
  */
}

/*
  //bei oberem Bit==1 untere Bit umdrehen, so bekommen wir eine zählbare Reihenfolge zum Encoder-Bit-Muster
  //PLUS        //MINUS
  11  (10)      11  (10)
  10  (11)      01  (01)
  00  (00)      00  (00)
  01  (01)      10  (11)
*/

void ISR_phase(void) {
  byte static old_wert = 2; //keine Phase aktiv, Beide per PullUP hochgezogen, untere Bit umgedreht
  byte new_wert = digitalRead(pin_phase_b) << 1 | digitalRead(pin_phase_a);
  if (new_wert & 0x02) new_wert ^= 0x01; //wenn oberes Bit gesetzt ist, das Untere umdrehen
  if (new_wert == ((old_wert + 1) & 0x03)) {
    counter++;
    change = true;
  }
  if (old_wert == ((new_wert + 1) & 0x03)) {
    counter--;
    change = true;
  }
  old_wert = new_wert;
}

Schon hier ist es für mich kein größeres Problem, daß Schritte verloren gehen.
Die Reduzierung der Serial.print's könnte noch helfen, aber robuster wird's damit auch nicht wirklich.