2014/02/11

Arduino練習:旋轉編碼器

旋轉編碼器(rotary encoder)可將旋轉位置或旋轉量轉變成訊號(類比或數位),透過某種方式(機械、光學、磁力等),得知轉軸轉動了,發出訊號通知我們。可分為絕對型(absolute)及增量型(incremental)或稱為相對型(relative),絕對型將轉軸的不同位置一一編號,然後根據目前位置輸出編號;增量型編碼器則是當轉軸旋轉時輸出變化,轉軸不動就沒有輸出。

底下是我購買的增量型旋轉編碼器模組,可以一直旋轉,旋轉一整周被分為20小格,正轉逆轉皆可,當轉動時,CLK腳位就會呈低電位,此時可讀取DT資料腳位,若為HIGH代表正轉,LOW代表逆轉;SW則是開關腳位,轉軸可被按下改變此腳位的狀態。另外+需接正電源,GND接地。

這種編碼器屬於機械式,需要對訊號進行去抖動,較不精準、適合低轉速場合;光學式則可用於高轉速或精確度需求較高的地方。

增量型旋轉編碼器會有類似齒輪缺口與感應的機制,以此可有兩個輸出A與B,旋轉時,A與B將輸出不同的訊號,正轉與逆轉可得到不同的一串訊號,如此便可區分正逆轉。
不過我購買的模組已經處理好了,不需要自己偵測A與B作處理與判斷。

我用的板子是Arduino Uno R3,軟體開發環境版本為1.0.5、Windows版。

電路如下:
Arduino腳位2,連接模組的CLK。
Arduino腳位3,連接模組的DT。
Arduino腳位4,連接模組的SW。

草稿碼如下,當偵測到正轉時,就增加全域變數count、並輸出到序列埠,逆轉則減少,按下開關則歸零。

#define SERIAL_BAUDRATE 115200
#define CLK_PIN 2 // 定義連接腳位
#define DT_PIN 3
#define SW_PIN 4

#define interruptA 0 // UNO腳位2是interrupt 0,其他板子請見官方網頁

volatile long count = 0;
unsigned long t = 0;

void setup() {
  Serial.begin(SERIAL_BAUDRATE);
  // 當狀態下降時,代表旋轉編碼器被轉動了
  attachInterrupt(interruptA, rotaryEncoderChanged, FALLING);
  pinMode(CLK_PIN, INPUT_PULLUP); // 輸入模式並啟用內建上拉電阻
  pinMode(DT_PIN, INPUT_PULLUP);
  pinMode(SW_PIN, INPUT_PULLUP);
}
void loop() {
  if(digitalRead(SW_PIN) == LOW){ // 按下開關,歸零
     count = 0; 
     Serial.println("count reset to 0");
     delay(300);
  }
}
void rotaryEncoderChanged(){ // when CLK_PIN is FALLING
  unsigned long temp = millis();
  if(temp - t < 200) // 去彈跳
    return;
  t = temp;
 
  // DT_PIN的狀態代表正轉或逆轉
  count += digitalRead(DT_PIN) == HIGH ? 1 : -1;
  Serial.println(count);
}

執行後,開啟序列埠監控視窗,用手轉動旋轉編碼器,正轉時可看到不斷變大的數字,逆轉則減少。不過有時會出現錯誤,應該加1的情況卻先加1、再減1、再加1,不夠完美。

參考資料:

13 comments:

  1. Anonymous7/7/16 14:56

    我現在馬達上面有encoder的線,那樣的話我可以直接利用馬達有的線,去接收encoder的AB相資訊嗎?這樣接線要怎麼接

    ReplyDelete
    Replies
    1. 不知道。

      既然有encoder的線,請查閱馬達產品的說明書。

      Delete
  2. 前輩你好,搜尋過你的好幾篇文章都有使用過中斷函式,而好像有一篇有寫到中斷函式不能夠寫得讓時間拖得太久比如步進馬達的運轉,如果是這樣的話想請問,假如我接了一個磁簧開關的電路當開關未導通時步進馬達做逆時針旋轉,但開關一旦接通步進馬達立即停止並且作順時針轉的話,該怎麼寫呢?

    有查過前輩寫的這篇http://yehnan.blogspot.tw/2012/03/arduino.html只是感覺用if指令也要等到馬達轉完才有辦法執行下一步命令,無法達到立即停止。

    以下程式碼為失敗作:
    void setup() {
    pinMode(buttonPin, INPUT);
    pinMode(vPin, INPUT);
    pinMode(k, OUTPUT);
    attachInterrupt(interruptNumber,buttonStateChanged,CHANGE);
    stepper.setSpeed(110);
    Serial.begin(9600);


    }

    void loop() {

    val = analogRead(vPin);
    Serial.println(val);
    stepper.step(-800);
    delay(1000);
    }

    void buttonStateChanged() {
    buttonState = digitalRead(buttonPin);
    digitalWrite(k, buttonState);
    stepper.step(400);
    }

    ReplyDelete
    Replies
    1. Arduino內建程式庫Stepper,只提供阻斷式(blocking)函式。
      所以你呼叫stepper.step(400);,會停在該處,等到馬達轉動完畢,才會繼續往下執行。

      可試著改用另一套程式庫AccelStepper。

      Delete
    2. 感謝前輩的提示,改用AccelStepper程式庫的確有達到馬達中斷的功能了。
      只是仍然有個小問題,他在磁簧開關閉合的瞬間啟動了中斷功能停止馬達的轉動,但是馬達並沒有接著迴轉,需要手動再去把磁簧開關撥開馬達才會執行迴轉,想請問這會是什麼問題啊?

      程式碼:
      void setup() {

      stepper.setMaxSpeed(500);
      stepper.setAcceleration(500);
      pinMode(buttonPin, INPUT);
      pinMode(vPin, INPUT);
      pinMode(k, OUTPUT);
      attachInterrupt(interruptNumber,stepperHome,FALLING);
      Serial.begin(9600);

      }

      void loop() {

      val = analogRead(vPin);
      Serial.println(val);
      delay(200);
      stepper.moveTo(-1600);
      stepper.runToPosition();
      delay(1000);

      }


      void stepperHome() {

      buttonState = digitalRead(buttonPin);
      while (buttonState == HIGH) {
      stepper.moveTo(2400);
      stepper.run();
      digitalWrite(k, LOW);
      buttonState = digitalRead(buttonPin);
      }
      digitalWrite(k, HIGH);
      stepper.setCurrentPosition(0);
      }

      Delete
    3. 你的問題跟程式碼,搭不起來。
      請清楚描述想要的行為。

      buttonPin、vPin、k是什麼?

      你的程式寫法、應該不合AccelStepper的設計。
      應該某處設定想要轉到哪裡,steppermoveTo(xxx);,
      在loop()裡呼叫stepper.run();,並且不要使用delay(),
      這樣就會不斷進入loop()並呼叫run(),操控馬達轉動。

      Delete
    4. 抱歉,不知道怎麼附圖所以沒貼上電路圖,只好上傳網址http://ppt.cc/Slte9

      buttonPin是Arduino UNO的中斷腳位pin 2,vPin是類比A0(14)腳位打算量測磁簧開關導通過程的電壓變化,而至於k只是想知道buttonState的情況而接的LED燈。

      我的功能是希望馬達會先CCW轉動,然後隨著磁簧開關緩慢的閉合,vPin腳位會記錄這過程的電壓變化在val變數裡,而當導通瞬間HIGH訊號會觸發中斷的buttonPin,讓馬達開始迴轉。


      #include

      AccelStepper stepper(4, 11, 10, 9, 8);
      const int buttonPin = 2;
      float val = 0.00;
      const int vPin = 14;
      const int k = 7;
      volatile int buttonState;
      const int interruptNumber = 0;

      void setup() {
      stepper.setMaxSpeed(500);
      stepper.setAcceleration(500);
      pinMode(buttonPin, INPUT);
      pinMode(vPin, INPUT);
      pinMode(k, OUTPUT);
      attachInterrupt(interruptNumber,stepperHome,FALLING);
      Serial.begin(9600);

      }

      void loop() {

      val = analogRead(vPin);
      Serial.println(val);
      stepper.moveTo(-1600);
      stepper.run(); // 2016 07 21 新增
      stepper.runToPosition();

      }


      void stepperHome() {

      buttonState = digitalRead(buttonPin);
      while (buttonState == HIGH) {
      stepper.moveTo(2400);
      stepper.run();
      digitalWrite(k, LOW);
      buttonState = digitalRead(buttonPin);
      }
      digitalWrite(k, HIGH);
      stepper.setCurrentPosition(0);
      }

      Delete
    5. stepperHome是什麼時候才會被呼叫?
      磁簧開關閉合的時候?

      Delete
    6. http://pastebin.com/jHdtN4Hf
      或許可行,沒真正試過,後果自負。

      Delete
    7. 還真的可以用,太感謝了。
      只是有些程式語言和前面說的阻斷式還是有些不懂,葉老師有出這方面的書可以讀的嗎?

      Delete
    8. 上份程式碼使用「狀態機」的程式技巧。
      請參考拙作
      Arduino輕鬆入門:範例分析與實作設計
      http://yehnan.blogspot.tw/2014/02/arduino_21.html

      Delete
  3. Anonymous1/8/16 17:12

    想請問上面說的AccelStepper library 有辦法計算自己走了多少steps嗎?
    還是一定需要接個旋轉編碼器才有辦法?

    ReplyDelete
    Replies
    1. 嗯,
      long AccelStepper::currentPosition() 可知道目前在哪,
      前後相減,應就知道走了幾step。

      Delete