元高専生のロボット作り

元高専生のロボット作り

主にプログラミング, 電子系について書きます。たまに機械系もやります。メモ代わりの記事ばっか書きます

Arduinoでロータリーエンコーダーを使う

モーター制御しようとすると角度を取得する何らかのセンサーが必要です。

今回はその中でもロータリーエンコーダー(インクリメンタル型)をArduinoで使ってみました。

ピン変化割り込みを使用しているので多少高速でも精度よく角度が読み取れると思います。

使用したロータリーエンコーダーはこれです。
岩通マニュファクチャリング EC202A050A ロータリーエンコーダ


とりあえず先にコードを載せます。
まずライブラリのヘッダーファイルから

//RotaryEncoder.h
#ifndef RotaryEncoder_h
#define RotaryEncoder_h
#include "Arduino.h"

class RotaryEncoder
{
public:
  RotaryEncoder(int pin1, int pin2, void (*fptr)() );
  long  getPosition();
  float  getAngle();
  void setPosition(long newPosition);
  void tick(void);
private:
  int _pin1, _pin2;
  int8_t _oldState;
  long _position;
};

#endif

ソースファイル

//RotaryEncoder.cpp
#include "Arduino.h"
#include "RotaryEncoder.h"

const int8_t TABLE[] = {0, -1,  1,  0, 1,  0,  0, -1, -1,  0,  0,  1, 0,  1, -1,  0  };

RotaryEncoder::RotaryEncoder(int pin1, int pin2, void (*fptr)() )
{
  _pin1 = pin1;
  _pin2 = pin2;
  pinMode(_pin1, INPUT);
  pinMode(_pin2, INPUT);
  digitalWrite(_pin1, INPUT_PULLUP);
  digitalWrite(_pin2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(_pin1), fptr, CHANGE);
  attachInterrupt(digitalPinToInterrupt(_pin2), fptr, CHANGE);

  _oldState = 3;
  _position = 0;
}

long  RotaryEncoder::getPosition() {
  return _position;
}

float  RotaryEncoder::getAngle() {
  return (float)_position * 360/200;
}

void RotaryEncoder::tick(void)
{
  int sig1 = digitalRead(_pin1);
  int sig2 = digitalRead(_pin2);
  int8_t thisState = sig1 | (sig2 << 1);

  if (_oldState != thisState) {
    _position += TABLE[thisState | (_oldState<<2)];
    _oldState = thisState;
  }
}

メインです

#include "RotaryEncoder.h"

void tickPath();
RotaryEncoder encoder = RotaryEncoder(2, 3, &tickPath);

void tickPath()
{
  encoder.tick();
}
void setup()
{
  Serial.begin(57600);
}

float angle = 0;

void loop()
{
  angle = encoder.getAngle();
  Serial.println(angle);
}

メインで変なところありますね。この部分です。

void tickPath();
RotaryEncoder encoder = RotaryEncoder(2, 3, &tickPath);

void tickPath()
{
  encoder.tick();
}

これは、理想的にはソースファイル内でこう書きたいんですけど

RotaryEncoder::RotaryEncoder(int pin1, int pin2)
{
  _pin1 = pin1;
  _pin2 = pin2;
  pinMode(_pin1, INPUT);
  pinMode(_pin2, INPUT);
  digitalWrite(_pin1, INPUT_PULLUP);
  digitalWrite(_pin2, INPUT_PULLUP);

/*ここをこんな感じで書きたかった*************************************************/
  attachInterrupt(digitalPinToInterrupt(_pin1), RotaryEncoder::tick, CHANGE);
  attachInterrupt(digitalPinToInterrupt(_pin2), RotaryEncoder::tick, CHANGE);
/**************************************************************************************/

  _oldState = 3;
  _position = 0;
}

でも、できないんですよ...
attachInterruptをclass内で使用するのどうやればいいんですか?



他の部分のプログラムはまあ簡単だと思います。

一応、解説すると、

AとB、2相のロータリーエンコーダーを4逓倍にして扱いたいので、

A, Bそれぞれのピンの変化の流れを表にすると

前回(_oldState) 00 00 00 00 01 01 01 01 10 10 10 10 11 11 11 11
今回(thisState) 00 01 10 11 00 01 10 11 00 01 10 11 00 01 10 11
合計(thisState | (_oldState<<2)) 0 1 10 11 100 101 110 111 1000 1001 1010 1011 1100 1101 1110 1111
10進数 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
状態 変化なし 減少 増加 × 増加 変化なし × 減少 減少 × 変化なし 増加 × 増加 減少 変化なし
_position増加量 0 -1 1 0 1 0 0 -1 -1 0 0 1 0 1 -1 0

となります。

前回と今回の合計(thisState | (_oldState<<2))がindexとなって_positionの増加量となるようなTABLEをつくってやれば良いので、
関数tickのようになります。