Cách đồng bộ hóa hai bộ truyền động tuyến tính bằng Arduino

Chuyển động đồng bộ giữa các bộ truyền động tuyến tính khác nhau có thể rất quan trọng đối với sự thành công của một số ứng dụng của khách hàng, một trong những ứng dụng phổ biến là hai bộ truyền động tuyến tính mở một cửa sập. Để đạt được điều này, chúng tôi khuyên bạn nên sử dụng Firgelli chuyên dụng hộp điều khiển đồng bộ FA-SYNC-2 và FA-SYNC-4. Tuy nhiên, một số người tự làm và tin tặc thích sự tự do mà một bộ vi điều khiển như Arduino cung cấp và thay vào đó thích viết chương trình điều khiển đồng bộ của riêng họ. Hướng dẫn này nhằm mục đích cung cấp một cái nhìn tổng quan về cách đạt được điều này bằng cách sử dụng Bộ truyền động tuyến tính dòng quang.

Lời tựa

Hướng dẫn này không phải là cách xử lý nghiêm ngặt các bước cần thiết để đạt được điều khiển đồng bộ với Arduino, mà là tổng quan rộng rãi để hỗ trợ bạn viết chương trình tùy chỉnh của riêng mình. Hướng dẫn này nâng cao và giả sử bạn đã quen thuộc với phần cứng, phần mềm Arduino và lý tưởng là có kinh nghiệm với các tín hiệu điều chế độ rộng xung (PWM), quy trình dịch vụ ngắt (ISR), gỡ lỗi cảm biến và bộ mã hóa động cơ. Ví dụ được cung cấp trong hướng dẫn này là một bộ điều khiển tỷ lệ nguyên thủy. Nhiều cải tiến có thể được thực hiện trên ví dụ sau bao gồm, nhưng không giới hạn ở: triển khai vòng điều khiển PID và mở rộng quy mô đến nhiều hơn hai bộ truyền động tuyến tính. Xin lưu ý rằng chúng tôi không có tài nguyên để cung cấp hỗ trợ kỹ thuật cho các ứng dụng Arduino và sẽ không gỡ lỗi, chỉnh sửa, cung cấp mã hoặc sơ đồ đấu dây bên ngoài các hướng dẫn có sẵn công khai này.

Tổng quan Điều khiển đồng bộ

Điều khiển đồng bộ đạt được bằng cách so sánh chiều dài của hai bộ truyền động tuyến tính và điều chỉnh tốc độ theo tỷ lệ; nếu một bộ truyền động bắt đầu chuyển động nhanh hơn bộ truyền động khác, chúng tôi sẽ làm chậm nó lại. Chúng ta có thể đọc vị trí của bộ truyền động tuyến tính thông qua bộ mã hóa quang học có sẵn. Bộ mã hóa quang học là một đĩa nhựa nhỏ có 10 lỗ trên đó được kết nối với động cơ DC để khi động cơ quay, đĩa nhựa cũng quay. Một đèn LED hồng ngoại được hướng về phía đĩa nhựa để khi nó quay ánh sáng sẽ truyền qua các lỗ trên đĩa quang hoặc bị chặn bởi nhựa của đĩa. Một cảm biến hồng ngoại ở phía bên kia của đĩa phát hiện khi ánh sáng truyền qua lỗ và xuất ra tín hiệu sóng vuông. Bằng cách đếm số lượng xung mà bộ thu phát hiện được, chúng ta có thể tính cả RPM của động cơ và khoảng cách mà bộ truyền động tuyến tính đã đi được. Bộ truyền động tuyến tính quang học 35lb có 50 (+/- 5) xung quang trên mỗi inch di chuyển trong khi bộ truyền động 200lb và 400lb đều có 100 (+/- 5) xung mỗi inch. Bằng cách so sánh mức độ mở rộng của mỗi bộ truyền động tuyến tính, chúng tôi có thể điều chỉnh tỷ lệ tốc độ của hai bộ truyền động sao cho chúng luôn ở cùng một chiều dài trong khi kéo dài.

Các thành phần bắt buộc

Sơ đồ hệ thống dây điện

Cách đồng bộ hóa hai bộ truyền động tuyến tính bằng Arduino

Thực hiện các kết nối dây trên. Luôn kiểm tra các màu dây ra khỏi bộ truyền động tuyến tính vì quy ước về màu có thể thay đổi so với những gì được thể hiện trong sơ đồ trên.

    Hướng dẫn nhanh

    Nếu bạn chỉ muốn hai thiết bị truyền động tuyến tính của mình chuyển động đồng bộ, chỉ cần làm theo các bước sau:

    • Thực hiện các kết nối như thể hiện trong sơ đồ nối dây.
    • Tải lên và chạy chương trình đầu tiên, bên dưới.
    • Sao chép hai giá trị do chương trình này xuất ra vào dòng 23 của chương trình thứ hai, bên dưới.
    • Tải lên và chạy chương trình thứ hai.
    • Tinh chỉnh hệ thống của bạn bằng cách thay đổi biến K_p (dòng 37, chương trình thứ hai). Điều này dễ dàng thực hiện nhất bằng cách gắn một chiết áp vào chân analog A0 và sửa đổi mã để đọc chiết áp và sử dụng hàm map (): K_p = map (analogRead (A0), 0, 1023, 0, 20000);

    Phần còn lại của hướng dẫn này sẽ trình bày chi tiết hơn một số tính năng chính của chương trình. Một lần nữa, chúng tôi nhắc lại rằng đây không phải là một hướng dẫn đầy đủ, mà là một tổng quan về những điều cần xem xét khi tạo chương trình của riêng bạn.

    Tổng quan về Chương trình Hiệu chuẩn

    Trước khi có thể đạt được điều khiển đồng bộ, trước tiên chúng ta phải hiệu chỉnh hệ thống. Điều này liên quan đến việc đếm số lượng xung trên mỗi chu kỳ hoạt động vì như đã nêu trong thông số kỹ thuật của sản phẩm có dung sai là (+/- 5) xung trên mỗi inch di chuyển. Tải lên và chạy chương trình, bên dưới. Chương trình này sẽ thu lại hoàn toàn các bộ truyền động (dòng 53) và đặt biến bộ đếm xung quang về 0, sau đó nó sẽ mở rộng hoàn toàn và thu lại hoàn toàn (dòng 63 và 74, tương ứng). Trong chu kỳ khởi động này, số lượng xung sẽ được đếm bởi quy trình dịch vụ ngắt (ISR), dòng 153 và 166. Sau khi chu kỳ khởi động hoàn tất, số lượng xung trung bình sẽ được xuất ra, dòng 88, hãy ghi lại các giá trị này cho lần sau.

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

    Tổng quan về chương trình đồng bộ

    Trước khi tải lên chương trình điều khiển đồng bộ, trước tiên bạn phải sao chép các giá trị do chương trình hiệu chuẩn đầu ra vào dòng 23 và thay thế mảng hiện tại: {908, 906} bằng các giá trị của riêng bạn. Ngoài ra, nếu bạn đang sử dụng bộ truyền động tuyến tính 35lb, bạn sẽ cần thay đổi giá trị của biến ở dòng 29 từ 20 mili giây thành 8 mili giây.

    Sau khi rút lại hoàn toàn một lần (để xác định điểm gốc), bạn có thể di chuyển đồng bộ cả hai bộ truyền động tuyến tính bằng cách nhấn ba nút tương ứng với các lệnh kéo dài, rút ​​lại và dừng. Các bộ truyền động sẽ vẫn đồng bộ ngay cả khi tải không đồng đều bằng cách so sánh các bộ đếm xung tương đối của chúng và điều chỉnh tốc độ giữa chúng để luôn duy trì đồng bộ. Xin lưu ý rằng chương trình hiện tại thực hiện một bộ điều khiển tỷ lệ đơn giản, dòng 93, vì vậy có thể bị vọt lố và dao động xung quanh điểm cân bằng. Bạn có thể điều chỉnh điều này bằng cách thay đổi biến K_p, được xác định ở dòng 37. Điều này dễ dàng thực hiện nhất bằng cách gắn một chiết áp vào chân analog A0 và sửa đổi mã để đọc chiết áp và sử dụng hàm map (): K_p = map (analogRead (A0), 0, 1023, 0, 20000);

    Để có kết quả tốt nhất, chúng tôi đặc biệt khuyên bạn nên loại bỏ bộ điều khiển tỷ lệ và thực hiện một vòng điều khiển PID; tuy nhiên điều này nằm ngoài phạm vi của hướng dẫn giới thiệu này và đã được cố tình bỏ qua.

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

    Sử dụng Bộ truyền động Bullet 36 và Bullet 50 trong đồng bộ

    Ngoài thiết bị truyền động tuyến tính Dòng quang của chúng tôi, chúng tôi cũng cung cấp hai thiết bị truyền động tuyến tính cung cấp với bộ mã hóa sẵn có: Bullet 36 Cal. và Bullet 50 Cal, cả hai đều có bộ mã hóa Hiệu ứng Hall vuông góc bên trong. Bộ mã hóa Hall Effect hoạt động trên nguyên tắc tương tự như bộ mã hóa quang học, tuy nhiên thay vì sử dụng ánh sáng, nó sử dụng từ tính. Hơn nữa, là một bộ mã hóa cầu phương, nó có hai đầu ra tín hiệu, mỗi đầu ra lệch pha nhau 90 độ. Do đó, bạn cần sử dụng bảng Arduino có 4 chân ngắt trở lên (Arduino Uno chỉ có hai chân) và sửa đổi mã để xử lý đầu vào từ hai tín hiệu trên mỗi bộ truyền động. Hơn nữa, biến thời gian gỡ lỗi, falsepulseDelay sẽ cần được điều chỉnh cùng với K_p.

    Mẹo viết chương trình của riêng bạn

    Nhiều hơn hai thiết bị truyền động tuyến tính

    Khi sử dụng hai hoặc nhiều bộ truyền động tuyến tính, Arduino Uno sẽ không hoạt động nữa vì nó chỉ có sẵn hai chân ngắt. Bạn sẽ cần sử dụng bảng Arduino có sẵn số lượng chân ngắt phù hợp, thêm thông tin: https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

    Thứ hai, vì lợi ích của hiệu quả, bạn nên vectơ hóa chương trình của mình bằng cách sử dụng các mảng và vòng lặp for () để lặp lại trên mỗi bộ truyền động.

    Debouncing

    Như với nhiều cảm biến, điều quan trọng là phải nhận biết được các tín hiệu dội lại. Giống như các công tắc cơ học, các bộ mã hóa cũng có thể bị nảy. Trong ví dụ trên, quá trình gỡ lỗi đã được xử lý bởi một độ trễ đơn giản (được xác định bởi biến falsepulseDelay), điều quan trọng là phải xử lý điều này trong bất kỳ thay đổi phần mềm nào bạn thực hiện hoặc với mạch vật lý để lọc ra tiếng ồn dội lại.

    Xử lý cuộn qua

    Nếu bạn sửa đổi mã, hãy lưu ý đến việc di chuột qua khi xử lý hàm millis (). Cả mảng millis () và lastDebounceTime đều được khai báo là các biến dài không dấu có nghĩa là chúng có thể lưu trữ các giá trị lên đến 4,294,967,295 (32 ^ 2-1). Điều này có nghĩa là khoảng thời gian quay vòng là 49,7 ngày. Chương trình hiện tại được thiết kế để xử lý cuộn qua trong các hàm ISR (quy trình dịch vụ ngắt): count_0 & count_1, tuy nhiên nếu bạn sửa đổi chương trình này, hãy đảm bảo xử lý chính xác quá trình cuộn qua biến, nếu không chương trình của bạn sẽ bị lỗi sau ~ 49,7 ngày sử dụng liên tục. Để biết thêm thông tin tham khảo: https://www.norwegiancreations.com/2018/10/arduino-tutorial-avoiding-the-overflow-issue-when-using-millis-and-micros/

     

    Share This Article
    Tags: