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,不夠完美。

參考資料:

34 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
  4. 請問您有提到"適合低轉速",一般腳踏車的轉速合適嗎?

    ReplyDelete
  5. 葉老師您好:昨天有像你詢問伺服馬達問題,另外想說關於ARDUINO它有辦法從外部零件改變設定值嗎?!比如說寫一個伺服馬達控制角度或數值,但是我想用外部零件去更改它的角度或數值就好,不用在接usb設定後在整個sketch燒進去uno這樣。謝謝

    ReplyDelete
    Replies
    1. 當然可以。

      加可變電阻,讀取其值,會是0~1023,使用map映射到你想要的範圍,
      然後設定變數。
      變數改變後,再根據變數去動馬達的角度。

      Delete
    2. 不不,我是想說數值直接透過外部零件改變,斷電後數值也會保留,所以用外部零件更改數值然後放EEPROM這方法是可以的?
      因為有看到EEPROM那篇,沒有提到能不能利用外部零件做數值改變才來問一下葉老師這樣。謝謝

      Delete
    3. 你之前又沒提到EEPROM。

      > 然後放EEPROM這方法是可以的?
      可以,為什麼不行。就把數值存入EEPROM,下次開機時讀出來。

      Delete
    4. 哈哈,抱歉抱歉,沒有把疑問打清楚,那我在試看看。謝謝

      Delete
  6. 請問 這是可以 360 度 旋轉的嗎?
    像滑鼠滾輪那樣

    ReplyDelete
    Replies
    1. 內文:
      可以一直旋轉,旋轉一整周被分為20小格,正轉逆轉皆可

      Delete
  7. This comment has been removed by a blog administrator.

    ReplyDelete
  8. This comment has been removed by a blog administrator.

    ReplyDelete
  9. 葉老師您好,我也出現Encoder有時候會先加1減1再加1的情況,請問這是因為loop不夠快嗎?

    ReplyDelete
  10. Thanks for sharing this forum post!! it was very informative and helpful!! All your posts have very relevant content and are resourceful! Keep posting! looking forward to your future posts
    valentine week list 2018
    valentine week 2018
    valentine's week 2018
    valentine's day 2018
    Chocolate Day 2018
    Rose Day 2018
    Propose Day 2018
    Chocolate Day 2018
    Teddy Day 2018
    Promise Day 2018
    Hug Day 2018
    Kiss Day 2018

    ReplyDelete
  11. 請問我要怎麼知道轉到第幾步?
    能不能Serial.println說我現在轉到第幾格了?

    ReplyDelete
    Replies
    1. 使用變數紀錄,一開始是0,隨著轉動增減。

      Delete
    2. 不好意思我昨天可能問的不太清楚
      因為我是arduino新手
      很多程式都是參考葉難大大的文章及網路高手
      來完成自己想要控制的東西
      下面是我想問的問題比較詳細的問法
      還麻煩葉難大大幫我解惑一下

      #include
      #define STEPS 2048 我確定我的步進馬達轉一圈為2048步

      Stepper stepper(STEPS, 8, 9, 10,11);//我的馬達腳位設定(步數.腳位.腳位.腳位)

      void setup()
      {
      Serial.begin(9600);
      Serial.println(" angle test ");
      stepper.setSpeed(11); // 我將馬達的速度設定成11RPM
      }

      void loop()
      {
      stepper.step(STEPS);//馬達正圈2048步
      delay(1000);
      stepper.step(- STEPS);//馬達反圈2048步
      delay(1000);
      //到這邊讓馬達轉到一開始設定的角度我都沒什麼問題
      //但是我想要知道的是的
      //stepper.step(X)這句的意思好像是直接連續跑到我要指定的 X 步數
      //那我能直接在"序列埠監控視窗中"
      //即時顯示出我的馬達正轉到第幾步嗎?或是秀出轉幾度
      //因為我想要讓我的馬達在轉的時候
      //感應到東西時記住轉到第幾步或是轉多少度
      //請問這樣子是否可行我該怎麼做 ?

      }

      Delete
    3. Stepper程式庫很難做到。
      改用https://github.com/adafruit/AccelStepper這套程式庫吧。

      呼叫currentPosition()可以取得目前走到的位置。

      Delete
    4. 好的我研究看看謝謝大大堤點

      Delete