Arduino Kullanarak İki Doğrusal Aktüatör Nasıl Eşitlenir

Çoklu lineer aktüatörler arasındaki senkron hareket, bazı müşteri uygulamalarının başarısı için hayati öneme sahip olabilir, bunlardan biri, bir kapıyı açan iki lineer aktüatördür. Bunu başarmak için özel Firgelli'yi kullanmanızı öneririz. senkron kontrol kutusu FA-SYNC-2 ve FA-SYNC-4. Ancak bazı DIY yapanlar ve bilgisayar korsanları, Arduino gibi bir mikrodenetleyicinin sunduğu özgürlüğü tercih ediyor ve bunun yerine kendi senkronize kontrol programlarını yazmayı tercih ediyor. Bu eğitim, bunu kullanarak bunu nasıl başaracağınıza genel bir bakış sağlamayı amaçlamaktadır. Optik Serisi doğrusal aktüatör.

Önsöz

Bu eğitim, Arduino ile senkronize kontrol elde etmek için gereken adımların titiz bir şekilde ele alınması değil, kendi özel programınızı yazmanıza yardımcı olacak geniş bir genel bakış niteliğindedir. Bu eğitim ileri düzeydedir ve Arduino donanımına, yazılımına zaten aşina olduğunuzu ve ideal olarak darbe genişlik modülasyonu (PWM) sinyalleri, kesinti servis rutini (ISR), sensörlerin hata bildirimi ve motor kodlayıcıları konusunda deneyim sahibi olduğunuzu varsayar. Bu öğreticide verilen örnek, ilkel bir orantılı denetleyicidir. Aşağıdaki örnekte, bunlarla sınırlı olmamak üzere, birçok iyileştirme uygulanabilir: bir PID kontrol döngüsünün uygulanması ve ikiden fazla doğrusal aktüatör için ölçeklendirme. Lütfen Arduino uygulamaları için teknik destek sağlayacak kaynaklara sahip olmadığımızı ve bu halka açık eğitimlerin dışında hata ayıklama, düzenleme, kod veya bağlantı şemaları sağlamayacağımızı unutmayın.

Genel Bakış Senkron Kontrol

Senkron kontrol, iki doğrusal aktüatörün uzunluğunun karşılaştırılması ve hızın orantılı olarak ayarlanmasıyla elde edilir; bir aktüatör diğerinden daha hızlı hareket etmeye başlarsa onu yavaşlatırız. Doğrusal aktüatörün konumunu dahili optik kodlayıcı aracılığıyla okuyabiliriz. Optik kodlayıcı, DC motora bağlanan, içinde 10 delik bulunan küçük bir plastik disktir, öyle ki motor döndüğünde plastik disk de döner. Bir kızılötesi LED, plastik diske doğru yönlendirilir, böylece ışığı döndürürken optik diskteki deliklerden iletilir veya diskin plastiği tarafından engellenir. Diskin diğer tarafındaki bir kızılötesi sensör, ışığın delikten ne zaman iletildiğini algılar ve bir kare dalga sinyali verir. Alıcının algıladığı darbe sayısını sayarak, hem motorun RPM'sini hem de doğrusal aktüatörün kat ettiği mesafeyi hesaplayabiliriz. 35 lb optik doğrusal aktüatör, hareketin inç başına 50 (+/- 5) optik darbesine sahipken, 200 lb ve 400 lb aktüatörlerin her ikisi de inç başına 100 (+/- 5) darbeye sahiptir. Her bir lineer aktüatörün ne kadar uzadığını karşılaştırarak, iki aktüatörün hızını orantılı olarak, uzarken her zaman aynı uzunlukta kalacak şekilde ayarlayabiliyoruz.

Gerekli Bileşenler

Bağlantı şeması

Arduino Kullanarak İki Doğrusal Aktüatör Nasıl Eşitlenir

Yukarıdaki kablo bağlantılarını yapın. Renklendirme kuralı yukarıdaki şemada gösterilenden farklı olabileceğinden, doğrusal aktüatörden çıkan kablo renklerini her zaman kontrol edin.

    Hızlı Eğitim

    Sadece iki lineer aktüatörünüzü senkronize hareket ettirmek istiyorsanız, aşağıdaki adımları uygulamanız yeterlidir:

    • Bağlantıları kablo şemasında gösterildiği gibi yapın.
    • Aşağıdaki ilk programı yükleyin ve çalıştırın.
    • Bu programın çıkardığı iki değeri aşağıdaki ikinci programın 23. satırına kopyalayın.
    • İkinci programı yükleyin ve çalıştırın.
    • K_p değişkenini (satır 37, ikinci program) değiştirerek sisteminize ince ayar yapın. Bu en kolay şekilde analog pin A0'a bir potansiyometre ekleyerek ve potansiyometreyi okumak için kodu değiştirerek ve map () işlevini kullanarak yapılır: K_p = map (analogRead (A0), 0, 1023, 0, 20000);

    Bu eğitimin geri kalanı, programların bazı temel özelliklerini daha ayrıntılı olarak ele alacaktır. Yine, bunun kapsamlı bir eğitim olmadığını, kendi programınızı oluştururken göz önünde bulundurmanız gereken şeylerin bir özeti olduğunu yineliyoruz.

    Kalibrasyon Programına Genel Bakış

    Eşzamanlı kontrole ulaşılmadan önce sistemi kalibre etmeliyiz. Bu, çalıştırma döngüsü başına darbe sayısının sayılmasını içerir, çünkü ürün özelliklerinde belirtildiği gibi, hareketin inç başına bir (+/- 5) darbe toleransı vardır. Aşağıdaki programı yükleyin ve çalıştırın. Bu program aktüatörleri tamamen geri çekecek (hat 53) ve optik darbe sayacı değişkenini sıfıra ayarlayacak ve daha sonra tamamen uzatılacak ve tamamen geri çekilecektir (sırasıyla satır 63 ve 74). Bu çalıştırma döngüsü sırasında, darbe sayısı, kesinti servis rutini (ISR), satır 153 ve 166 tarafından sayılacaktır. Çalıştırma döngüsü tamamlandıktan sonra, ortalama darbe sayısı çıkacaktır, satır 88, bu değerleri daha sonra not edin.

    https://gist.github.com/Will-Firgelli/89978da2585a747ef5ff988b2fa53904

    COPY
    /* Written by Firgelli Automations
     * Limited or no support: we do not have the resources for Arduino code support
     * This code exists in the public domain
     * 
     * Program requires two (or more) of our supported linear actuators:
     * FA-OS-35-12-XX
     * FA-OS-240-12-XX
     * FA-OS-400-12-XX
     * Products available for purchase at https://www.firgelliauto.com/collections/linear-actuators/products/optical-sensor-actuators
     */
    
    #include <elapsedMillis.h>
    elapsedMillis timeElapsed;
    
    #define numberOfActuators 2 
    int RPWM[numberOfActuators]={6, 11}; //PWM signal right side
    int LPWM[numberOfActuators]={5,10}; 
    int opticalPins[numberOfActuators]={2,3}; //connect optical pins to interrupt pins on Arduino. More information: https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
    volatile long lastDebounceTime_0=0; //timer for when interrupt was triggered
    volatile long lastDebounceTime_1=0;
    
    int Speed = 255; //choose any speed in the range [0, 255]
    
    #define falsepulseDelay 20 //noise pulse time, if too high, ISR will miss pulses. 
    volatile int counter[numberOfActuators]={}; 
    volatile int prevCounter[numberOfActuators]={}; 
    int Direction; //-1 = retracting
                   // 0 = stopped
                   // 1 = extending
    
    int extensionCount[numberOfActuators] = {}; 
    int retractionCount[numberOfActuators] = {}; 
    int pulseTotal[numberOfActuators]={}; //stores number of pulses in one full extension/actuation
    
    void setup(){
      for(int i=0; i<numberOfActuators; i++){
        pinMode(RPWM[i],OUTPUT);
        pinMode(LPWM[i], OUTPUT);
        pinMode(opticalPins[i], INPUT_PULLUP);
        counter[i]=0; //initialize variables as array of zeros
        prevCounter[i]=0;
        extensionCount[i] = 0;
        retractionCount[i] = 0;
        pulseTotal[i] = 0;
      }
      attachInterrupt(digitalPinToInterrupt(opticalPins[0]), count_0, RISING);
      attachInterrupt(digitalPinToInterrupt(opticalPins[1]), count_1, RISING); 
    
      Serial.begin(9600);
      Serial.println("Initializing calibration");
      Serial.println("Actuator retracting...");
      Direction = -1;
      moveTillLimit(Direction, 255); 
      Serial.println("Actuator fully retracted");
      delay(1000);
    
      for(int i=0; i<numberOfActuators; i++){
        Serial.print("\t\t\t\tActuator ");
        Serial.print(i);
      }
    
      Direction = 1;
      moveTillLimit(Direction, 255); //extend fully and count pulses
      Serial.print("\nExtension Count:");
      for(int i=0; i<numberOfActuators; i++){
      extensionCount[i]=counter[i];
      Serial.print("\t\t"); 
      Serial.print(extensionCount[i]);
      Serial.print("\t\t\t"); 
      }
      delay(1000);
    
      Direction = -1;
      moveTillLimit(Direction, 255); //retract fully and count pulses
      Serial.print("\nRetraction Count:");
      for(int i=0; i<numberOfActuators; i++){
      retractionCount[i]=counter[i];
      Serial.print("\t\t"); 
      Serial.print(abs(retractionCount[i]));
      Serial.print("\t\t\t"); 
      }
      Serial.print("\n");
    
      for(int i=0; i<numberOfActuators; i++){
        Serial.print("\nActuator ");
        Serial.print(i);
        Serial.print(" average pulses: ");
        pulseTotal[i]=(extensionCount[i]+abs(retractionCount[i]))/2; //takes the average of measurements
        Serial.print(pulseTotal[i]); 
      } 
      Serial.println("\n\nEnter these values in the synchronous control progam.");
    }
    
    void loop() { 
    }
    
    void moveTillLimit(int Direction, int Speed){
      //this function moves the actuator to one of its limits
      for(int i = 0; i < numberOfActuators; i++){
        counter[i] = 0; //reset counter variables
        prevCounter[i] = 0;
      } 
      do {
        for(int i = 0; i < numberOfActuators; i++) {
          prevCounter[i] = counter[i];
        }
        timeElapsed = 0;
        while(timeElapsed < 200){ //keep moving until counter remains the same for a short duration of time
          for(int i = 0; i < numberOfActuators; i++) {
            driveActuator(i, Direction, Speed);
          }
        }
      } while(compareCounter(prevCounter, counter)); //loop until all counts remain the same
    }
    
    bool compareCounter(volatile int prevCounter[], volatile int counter[]){
      //compares two arrays and returns false when every element of one array is the same as its corresponding indexed element in the other array
      bool areUnequal = true;
      for(int i = 0; i < numberOfActuators; i++){
        if(prevCounter[i] == counter[i]){
          areUnequal = false;
        } 
        else{ //if even one pair of elements are unequal the entire function returns true
          areUnequal = true;
          break;
        }
      }
      return areUnequal;
    }
    
    void driveActuator(int Actuator, int Direction, int Speed){
      int rightPWM=RPWM[Actuator];
      int leftPWM=LPWM[Actuator];
    
      switch(Direction){
      case 1: //extension
        analogWrite(rightPWM, Speed);
        analogWrite(leftPWM, 0);
      break;
    
      case 0: //stopping
        analogWrite(rightPWM, 0);
        analogWrite(leftPWM, 0);
      break;
    
      case -1: //retraction
        analogWrite(rightPWM, 0);
        analogWrite(leftPWM, Speed);
      break;
      }
    }
    
    void count_0(){
      //This interrupt function increments a counter corresponding to changes in the optical pin status
      if ((millis() - lastDebounceTime_0) > falsepulseDelay) { //reduce noise by debouncing IR signal 
        lastDebounceTime_0 = millis();
        if(Direction==1){
          counter[0]++;
        }
        if(Direction==-1){
          counter[0]--;
        }
      }
    }
    
    void count_1(){
      if ((millis() - lastDebounceTime_1) > falsepulseDelay) { 
        lastDebounceTime_1 = millis();
        if(Direction==1){
          counter[1]++;
        }
        if(Direction==-1){
          counter[1]--;
        }
      }
    }

    Senkron Programa Genel Bakış

    Eşzamanlı kontrol programını yüklemeden önce, ilk olarak kalibrasyon programı tarafından çıkılan değerleri 23. satıra kopyalamanız ve mevcut diziyi: {908, 906} kendi değerlerinizle değiştirmeniz gerekir. Ek olarak, 35 lb doğrusal aktüatör kullanıyorsanız, 29. satırdaki değişkenin değerini 20 milisaniyeden 8 milisaniyeye değiştirmeniz gerekecektir.

    Bir kez tamamen geri çekildikten sonra (orijini belirlemek için) her iki doğrusal aktüatörü uzatma, geri çekme ve durdurma komutlarına karşılık gelen üç düğmeye basarak senkronize olarak hareket ettirebilirsiniz. Aktüatörler, göreceli darbe sayaçlarını karşılaştırarak ve her zaman senkronize kalacak şekilde aralarındaki hızı ayarlayarak, eşit olmayan yükler altında bile senkronize kalacaktır. Mevcut programın basit bir orantısal kontrolör uyguladığı hatırlatılmalıdır, bu nedenle, bu nedenle denge etrafında aşma ve salınıma tabidir. Bunu, 37. satırda tanımlanan K_p değişkenini değiştirerek ayarlayabilirsiniz. Bu en kolay şekilde analog pin A0'a bir potansiyometre ekleyerek ve potansiyometreyi okumak için kodu değiştirerek ve map () işlevini kullanarak yapılır: K_p = map (analogRead (A0), 0, 1023, 0, 20000);

    En iyi sonuçlar için, orantılı denetleyiciyi kaldırmanızı ve bir PID kontrol döngüsü uygulamayı şiddetle tavsiye ederiz; ancak bu, bu giriş öğreticisinin kapsamı dışındadır ve kasıtlı olarak atlanmıştır.

    https://gist.github.com/Will-Firgelli/44a14a4f3cac3209164efe8abe3285b6

    COPY
    /* Written by Firgelli Automations
     * Limited or no support: we do not have the resources for Arduino code support
     * This code exists in the public domain
     * 
     */
    
    #include <elapsedMillis.h>
    elapsedMillis timeElapsed;
    
    #define numberOfActuators 2       
    int downPin = 7;
    int stopPin = 8;
    int upPin = 9;        
    int RPWM[numberOfActuators]={6, 11};                                  //PWM signal right side
    int LPWM[numberOfActuators]={5,10};        
    int opticalPins[numberOfActuators]={2,3};                             //connect optical pins to interrupt pins on Arduino. More information: https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
    volatile unsigned long lastDebounceTime[numberOfActuators]={0,0};     //timer for when interrupt is triggered
    int pulseTotal[numberOfActuators]={908, 906};                         //values found experimentally by first running two-optical-actuators-sync-calibration.ino
    
    int desiredSpeed=255;                            
    int adjustedSpeed;
    int Speed[numberOfActuators]={};    
    
    #define falsepulseDelay 20                                            //noise pulse time, if too high, ISR will miss pulses. If using 35lb actuator, set to 8ms                          
    volatile int counter[numberOfActuators]={};   
    volatile int prevCounter[numberOfActuators]={};     
    volatile float normalizedPulseCount[numberOfActuators]={};
    int Direction;                                                        //-1 = retracting
                                                                          // 0 = stopped
                                                                          // 1 = extending
    float error;
    int K_p=12000;                                                        //optimized experimentally. adjust this to fine tune your system
    int laggingIndex, leadingIndex;                                       //index of the slowest/fastest actuator
    
    void setup(){ 
      pinMode(stopPin, INPUT_PULLUP);
      pinMode(downPin, INPUT_PULLUP);
      pinMode(upPin, INPUT_PULLUP);
      for(int i=0; i<numberOfActuators; i++){
        pinMode(RPWM[i],OUTPUT); 
        pinMode(LPWM[i], OUTPUT);
        pinMode(opticalPins[i], INPUT_PULLUP);
        Speed[i]=desiredSpeed;
      }
      attachInterrupt(digitalPinToInterrupt(opticalPins[0]), count_0, RISING);
      attachInterrupt(digitalPinToInterrupt(opticalPins[1]), count_1, RISING);  
      Serial.begin(9600);
      Serial.println("Calibrating the origin");
      Serial.println("Actuator retracting...");
      Direction = -1;
      moveTillLimit(Direction, 255);  
      for(int i=0; i<numberOfActuators; i++){
        counter[i]=0;                                                     //reset variables 
        prevCounter[i]=0;
        normalizedPulseCount[i] = 0;
      }
      delay(1000);
      Serial.println("Actuator fully retracted");
    } 
    
    void loop() {  
      checkButtons();
    
      if(Direction==1){                                                   //based on direction of motion identify the leading and lagging actuator by comparing pulse counts
        if(normalizedPulseCount[0] < normalizedPulseCount[1]){
          laggingIndex = 0; 
          leadingIndex = 1;
        }
        else{
          laggingIndex = 1;
          leadingIndex = 0;
        }
      }
      else if(Direction==-1){
        if(normalizedPulseCount[0] > normalizedPulseCount[1]){
          laggingIndex = 0;
          leadingIndex = 1;
        }
        else{
          laggingIndex = 1;
          leadingIndex = 0;
        }
      }
      
      error=abs(normalizedPulseCount[laggingIndex]-normalizedPulseCount[leadingIndex]);
      if(Direction!=0){       
        adjustedSpeed=desiredSpeed-int(error*K_p);                 
        Speed[leadingIndex]=constrain(adjustedSpeed, 0, 255);               //slow down fastest actuator
        Speed[laggingIndex]=desiredSpeed;
      }
      for(int i=0; i<numberOfActuators; i++){
        Serial.print("  ");
        Serial.print(Speed[i]);
        Serial.print("  ");
        Serial.print(normalizedPulseCount[i]*1000);
        driveActuator(i, Direction, Speed[i]);
      }
      Serial.println();
    }
    
    void checkButtons(){
      //latching buttons: direction remains the same when let go
      if(digitalRead(upPin)==LOW){ Direction=1; }                           //check if extension button is pressed
      if(digitalRead(downPin)==LOW){ Direction=-1; }  
      if(digitalRead(stopPin)==LOW){ Direction=0; }
    }
    
    void moveTillLimit(int Direction, int Speed){
      //function moves the actuator to one of its limits
      for(int i = 0; i < numberOfActuators; i++){
        counter[i] = 0;                                                     //reset counter variables
        prevCounter[i] = 0;
      }  
      do {
        for(int i = 0; i < numberOfActuators; i++) {
          prevCounter[i] = counter[i];
        }
        timeElapsed = 0;
        while(timeElapsed < 200){                                           //keep moving until counter remains the same for a short duration of time
          for(int i = 0; i < numberOfActuators; i++) {
            driveActuator(i, Direction, Speed);
          }
        }
      } while(compareCounter(prevCounter, counter));                        //loop until all counters remain the same
    }
    
    bool compareCounter(volatile int prevCounter[], volatile int counter[]){
      //compares two arrays and returns false when every element of one array is the same as its corresponding indexed element in the other array
      bool areUnequal = true;
      for(int i = 0; i < numberOfActuators; i++){
        if(prevCounter[i] == counter[i]){
          areUnequal = false;
        } 
        else{                                                               //if even one pair of elements are unequal the entire function returns true
          areUnequal = true;
          break;
        }
      }
      return areUnequal;
    }
    
    void driveActuator(int Actuator, int Direction, int Speed){
      int rightPWM=RPWM[Actuator];
      int leftPWM=LPWM[Actuator];
      switch(Direction){
        case 1:       //extension
          analogWrite(rightPWM, Speed);
          analogWrite(leftPWM, 0);
          break;  
        case 0:       //stopping
          analogWrite(rightPWM, 0);
          analogWrite(leftPWM, 0);
          break;
        case -1:      //retraction
          analogWrite(rightPWM, 0);
          analogWrite(leftPWM, Speed);
          break;
      }
    }
    
    void count_0(){
      //This interrupt function increments a counter corresponding to changes in the optical pin status
      if ((millis() - lastDebounceTime[0]) > falsepulseDelay) {             //reduce noise by debouncing IR signal with a delay
        lastDebounceTime[0] = millis();
        if(Direction==1){
          counter[0]++;
        }
        if(Direction==-1){
          counter[0]--;
        }
        normalizedPulseCount[0]=float(counter[0])/float(pulseTotal[0]);
      }
    }
    
    void count_1(){
      if ((millis() - lastDebounceTime[1]) > falsepulseDelay) {   
        lastDebounceTime[1] = millis();
        if(Direction==1){
          counter[1]++;
        }
        if(Direction==-1){
          counter[1]--;
        }
        normalizedPulseCount[1]=float(counter[1])/float(pulseTotal[1]);
      } 
    }

    Bullet 36 ve Bullet 50 Aktüatörlerini Eşzamanlı Olarak Kullanma

    Optik Serisi doğrusal aktüatörümüze ek olarak, dahili kodlayıcılara sahip iki lineer aktüatör sunuyoruz: Bullet 36 Cal. ve Bullet 50 Cal, her ikisi de dahili bir dört evreli Hall Etkisi kodlayıcıya sahiptir. Hall Effect kodlayıcı, optik kodlayıcı ile aynı prensipte çalışır, ancak ışık kullanmak yerine manyetizmayı kullanır. Ayrıca, karesel bir kodlayıcı olarak, her biri 90 derece faz dışı olan iki sinyal çıkışına sahiptir. Bu nedenle, 4 veya daha fazla kesme pini olan bir Arduino kartı kullanmanız (Arduino Uno'da yalnızca iki tane vardır) ve aktüatör başına iki sinyalden girişi işlemek için kodu değiştirmeniz gerekir. Ayrıca, geri çevrilme süresi değişkeni olan falsepulseDelay'in K_p ile birlikte ayarlanması gerekecektir.

    Kendi Programınızı Yazmanın İpuçları

    İkiden fazla lineer aktüatör

    İki veya daha fazla lineer aktüatör kullanırken, Arduino Uno, yalnızca iki kesme pini mevcut olduğundan artık çalışmayacaktır. Uygun sayıda kesme pini bulunan bir Arduino kartı kullanmanız gerekecek, daha fazla bilgi: https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

    İkinci olarak, verimlilik açısından programlamanızı diziler ve for () döngüleri kullanarak vektörleştirerek her bir aktüatör üzerinde yinelemeniz önerilir.

    Debouncing

    Birçok sensörde olduğu gibi zıplayan sinyallerin de farkında olmak önemlidir. Mekanik anahtarlar olduğu gibi, kodlayıcılar da sıçrayan muzdarip olabilir. Yukarıdaki örnekte debouncing işlemi basit bir gecikme (falsepulseDelay değişkeni tarafından tanımlanan) tarafından ele alınmıştır, yaptığınız herhangi bir yazılım değişiklikleri veya fiziksel devre ile sıçrayan gürültü filtrelemek için bu işlemek için önemlidir.

    Döndürme devrilme

    Parolayı değiştirirseniz, millis() işlevi ile uğraşırken rollover'a dikkat edin. Hem millis() hem de lastDebounceTime dizisi imzasız uzun değişkenler olarak bildirilir, yani 4.294.967.295 (32^2-1) değeri depolayabilirler. Bu kabaca 49,7 günlük bir rollover süresi anlamına gelir. Geçerli program ISR (kesme hizmet yordamı) işlevlerinde rollover işlemek için tasarlanmıştır: count_0 & count_1, ancak bu programı değiştirirseniz değişken rollover doğru işlemek için emin olun, aksi takdirde program sürekli kullanım ~ 49,7 gün sonra çökecek. Daha fazla bilgi için bkz: https://www.norwegiancreations.com/2018/10/arduino-tutorial-avoiding-the-overflow-issue-when-using-millis-and-micros/

     

    Share This Article
    Tags: