Cara Menyinkronkan Dua Aktuator Linear menggunakan Arduino

Gerakan sinkron antara aktuator linier mutliple dapat menjadi vital bagi keberhasilan beberapa aplikasi pelanggan, salah satunya adalah dua aktuator linier yang membuka pintu jebakan. Untuk mencapai ini, kami merekomendasikan menggunakan Firgelli khusus kotak kontrol sinkron FA-SYNC-2 dan FA-SYNC-4. Namun beberapa DIYer dan peretas lebih memilih kebebasan yang ditawarkan mikrokontroler seperti Arduino dan lebih suka menulis program kontrol sinkron mereka sendiri. Tutorial ini bertujuan untuk memberikan gambaran umum tentang bagaimana mencapai ini dengan menggunakan Aktuator linier Seri Optik.

Kata pengantar

Tutorial ini bukanlah perawatan ketat dari langkah-langkah yang diperlukan untuk mencapai kontrol sinkron dengan Arduino, melainkan gambaran umum yang luas untuk membantu Anda menulis program kustom Anda sendiri. Tutorial ini maju dan mengasumsikan Anda sudah terbiasa dengan perangkat keras Arduino, perangkat lunak, dan idealnya memiliki pengalaman dengan sinyal modulasi lebar pulsa (PWM), rutin layanan interupsi (ISR), debouncing sensor, dan pengkode motor. Contoh yang diberikan dalam tutorial ini adalah pengontrol proporsional primitif. Banyak perbaikan dapat diimplementasikan pada contoh berikut termasuk, namun tidak terbatas pada: mengimplementasikan loop kontrol PID dan penskalaan ke lebih dari dua aktuator linier. Perlu diketahui bahwa kami tidak memiliki sumber daya untuk memberikan dukungan teknis untuk aplikasi Arduino dan tidak akan men-debug, mengedit, memberikan kode atau diagram pengkabelan di luar tutorial yang tersedia untuk umum ini.

Ikhtisar Kontrol Sinkron

Kontrol sinkron dicapai dengan membandingkan panjang dua aktuator linier dan menyesuaikan kecepatan secara proporsional; jika satu aktuator mulai bergerak lebih cepat dari yang lain, kami akan memperlambatnya. Kita dapat membaca posisi aktuator linier melalui encoder optik bawaan. Encoder optik adalah disk plastik kecil dengan 10 lubang di dalamnya yang terhubung ke motor DC sehingga saat motor berputar, disk plastik juga ikut berputar. Sebuah LED infra merah diarahkan ke piringan plastik sehingga saat itu berputar cahaya dengan baik ditransmisikan melalui lubang pada piringan optik atau diblokir oleh plastik piringan. Sensor infra merah di sisi lain dari disk mendeteksi ketika cahaya dipancarkan melalui lubang dan mengeluarkan sinyal gelombang persegi. Dengan menghitung jumlah pulsa yang dideteksi oleh penerima, kita dapat menghitung RPM motor dan jarak yang ditempuh oleh aktuator linier. Aktuator linier optik 35lb memiliki 50 (+/- 5) pulsa optik per inci perjalanan sedangkan aktuator 200lb dan 400lb keduanya memiliki 100 (+/- 5) pulsa per inci. Dengan membandingkan seberapa jauh setiap aktuator linier memanjang, kita dapat menyesuaikan kecepatan kedua aktuator secara proporsional sehingga keduanya selalu berada pada panjang yang sama saat memanjang.

Komponen yang Diperlukan

Diagram pengkabelan

Cara Menyinkronkan Dua Aktuator Linear menggunakan Arduino

Buat koneksi kabel di atas. Selalu periksa warna kabel yang keluar dari aktuator linier karena konvensi pewarnaan dapat berubah dari apa yang ditunjukkan pada diagram di atas.

    Tutorial Cepat

    Jika Anda hanya ingin dua aktuator linier Anda bergerak secara sinkron, cukup ikuti langkah-langkah berikut:

    • Buat koneksi seperti yang ditunjukkan pada diagram pengkabelan.
    • Unggah dan jalankan program pertama, di bawah ini.
    • Salin dua nilai yang dihasilkan oleh program ini ke baris 23 dari program kedua, di bawah ini.
    • Unggah dan jalankan program kedua.
    • Sempurnakan sistem Anda dengan memvariasikan variabel K_p (baris 37, program kedua). Hal ini paling mudah dilakukan dengan memasang potensiometer ke pin analog A0 dan memodifikasi kode untuk membaca potensiometer dan menggunakan fungsi map (): K_p = map (analogRead (A0), 0, 1023, 0, 20000);

    Sisa dari tutorial ini akan membahas lebih detail beberapa fitur utama dari program. Sekali lagi kami tegaskan bahwa ini bukan tutorial lengkap, melainkan gambaran umum tentang hal-hal yang perlu dipertimbangkan saat membuat program Anda sendiri.

    Gambaran Umum Program Kalibrasi

    Sebelum kontrol sinkron dapat dicapai, pertama-tama kita harus mengkalibrasi sistem. Ini melibatkan penghitungan jumlah pulsa per siklus aktuasi karena seperti yang dinyatakan dalam spesifikasi produk, terdapat toleransi (+/- 5) pulsa per inci perjalanan. Unggah dan jalankan program di bawah ini. Program ini akan menarik kembali aktuator sepenuhnya (baris 53) dan menyetel variabel penghitung pulsa optik ke nol, kemudian akan diperpanjang sepenuhnya dan ditarik sepenuhnya (baris 63 dan 74, masing-masing). Selama siklus aktuasi ini, jumlah pulsa akan dihitung oleh rutin layanan interupsi (ISR), baris 153 dan 166. Setelah siklus aktuasi selesai, jumlah rata-rata pulsa akan dikeluarkan, baris 88, catat nilai ini untuk nanti.

    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]--;
        }
      }
    }

    Tinjauan Program Sinkron

    Sebelum mengunggah program kontrol sinkron, Anda harus terlebih dahulu menyalin nilai-nilai yang dihasilkan oleh program kalibrasi ke dalam baris 23 dan mengganti larik saat ini: {908, 906} dengan nilai Anda sendiri. Selain itu, jika Anda menggunakan aktuator linier 35 pon, Anda perlu mengubah nilai variabel di baris 29 dari 20 milidetik menjadi 8 milidetik.

    Setelah mencabut sepenuhnya satu kali (untuk mengidentifikasi asal) Anda dapat memindahkan kedua aktuator linier secara sinkron dengan menekan tiga tombol yang sesuai dengan perintah ekstensi, pencabutan, dan penghentian. Aktuator akan tetap sinkron bahkan di bawah beban yang tidak rata dengan membandingkan penghitung pulsa relatifnya dan menyesuaikan kecepatan di antara keduanya agar selalu tetap sinkron. Perhatikan bahwa program saat ini mengimplementasikan pengontrol proporsional sederhana, baris 93, karena itu tunduk pada overshoot dan osilasi di sekitar ekuilibrium. Anda dapat menyesuaikan ini dengan memvariasikan variabel K_p, yang ditentukan di baris 37. Hal ini paling mudah dilakukan dengan memasang potensiometer ke pin analog A0 dan memodifikasi kode untuk membaca potensiometer dan menggunakan fungsi map (): K_p = map (analogRead (A0), 0, 1023, 0, 20000);

    Untuk hasil terbaik, kami sangat menyarankan untuk menghapus pengontrol proporsional dan menerapkan loop kontrol PID; namun ini di luar cakupan tutorial pengantar ini dan telah dihilangkan dengan sengaja.

    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]);
      } 
    }

    Menggunakan Bullet 36 dan Bullet 50 Actuator secara Synchronous

    Selain aktuator linier Seri Optik, kami juga menawarkan dua aktuator linier yang menawarkan enkoder bawaan: Bullet 36 Cal. dan Bullet 50 Cal, keduanya memiliki enkoder Efek Hall segi empat internal. Encoder Hall Effect bekerja dengan prinsip yang sama seperti encoder optik namun alih-alih menggunakan cahaya, ia menggunakan magnet. Selanjutnya menjadi encoder quadrature ia memiliki dua keluaran sinyal, masing-masing keluar fase sebesar 90 derajat. Dengan demikian Anda perlu menggunakan papan Arduino dengan 4 atau lebih pin interupsi (Arduino Uno hanya memiliki dua) dan memodifikasi kode untuk memproses input dari dua sinyal per aktuator. Selain itu, variabel debounce time, falsepulseDelay perlu disetel bersama dengan K_p.

    Tips untuk Menulis Program Anda Sendiri

    Lebih dari dua aktuator linier

    Saat menggunakan dua atau lebih aktuator linier, Arduino Uno tidak lagi berfungsi karena hanya tersedia dua pin interupsi. Anda perlu menggunakan papan Arduino dengan jumlah pin interupsi yang sesuai, informasi lebih lanjut: https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

    Kedua, untuk kepentingan efisiensi, disarankan untuk membuat vektor pemrograman Anda menggunakan array dan for () loop untuk mengulangi setiap aktuator.

    Debouncing

    Seperti banyak sensor, penting untuk menyadari sinyal yang memantul. Seperti sakelar mekanis, pembuat enkode juga dapat mengalami pantulan. Dalam contoh di atas, proses debouncing telah ditangani dengan penundaan sederhana (ditentukan oleh variabel falsepulseDelay), penting untuk menangani hal ini dalam setiap perubahan perangkat lunak yang Anda buat atau dengan sirkuit fisik untuk menyaring suara yang memantul.

    Penanganan berguling

    Jika Anda mengubah kode berhati-hatilah terhadap rollover saat berurusan dengan fungsi millis (). Baik milis () dan array lastDebounceTime dideklarasikan sebagai variabel panjang unsigned yang berarti mereka dapat menyimpan nilai hingga 4.294.967.295 (32 ^ 2-1). Ini berarti kira-kira periode rollover 49,7 hari. Program saat ini dirancang untuk menangani rollover di ISR ​​(rutinitas layanan interupsi): count_0 & count_1, namun jika Anda memodifikasi program ini, pastikan untuk menangani rollover variabel dengan benar, jika tidak program Anda akan macet setelah ~ 49,7 hari terus digunakan. Untuk informasi lebih lanjut, lihat: https://www.norwegiancreations.com/2018/10/arduino-tutorial-avoiding-the-overflow-issue-when-using-millis-and-micros/

     

    Share This Article
    Tags: