モーター制御しようとすると角度を取得する何らかのセンサーが必要です。
今回はその中でもロータリーエンコーダー(インクリメンタル型)を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のようになります。