ロータリエンコーダ付きステッピングモータをArduinoで制御して角度を取得する
機材
- Arduino UNO(中華)
- ロータリエンコーダ付きステッピングモータ
- PKP214U06A-R2EL
- PKP214U06A-R2EL-L|PKPシリーズ/PKシリーズ|ステッピングモーター|オリエンタルモーター株式会社
- モータ部説明書(PDF、HM-7433J.pdf)
- ロータリエンコーダ部説明書(PDF、HM-7439JE.pdf)
- 特性図(画像)(DC24V駆動時)
- モータ部
- 2相
- ユニポーラ5本リード線
- 基本ステップ角度1.8°
- AC 50/60Hz 0.5kV 絶縁耐圧(1分間)
- ロータリエンコーダ部
- 分解能 200 パルス/回転(pulse/revolution)
- A相、B相、Z相:3チャンネル出力
- DC5V駆動
- モータドライバ
- SLA7078MPRT
- SLA7078MPRT |サンケン電気
- 2相ステッピングモータードライバー ユニポーラ駆動用 SLA7078MPRT: 半導体 秋月電子通商-電子部品・ネット通販
- データシート(PDF、sla7073mprt_ds_jp.pdf)
- μステップ対応品
- 実使用電圧 10-44V
- モータドライバ基板
ステッピングモータの制御
ステッピングモータ 配線
ステッピングモータ本体からは、左から黒・緑・橙・青・赤の5本のケーブルが出ている。 これは説明書を見ると 黒・緑がA相(・)、 青・赤がB相(・)、 橙が電源になっている。 モータドライバ基板の対応する端子にこれらを接続する。
モータ電源として24V DC電源(ATS065-P240)を使用した。
ステッピングモータ ドライバ設定
μステップ機能(角度をより細かく制御できる)を使い、 励磁方式をW1-2相励磁(4分割)にするため、ドライバのM3端子をHIGHにする。 今回使ったドライバ基板ではDIPスイッチの4番をONにする。
ドライバ基板上の半固定抵抗(Refに接続)を使って、 カレントダウン(過熱防止のための電流カット機能)時の電流と 通常時の電流をできるだけ絞る(反時計回りで絞れる)。 実際に回すときは、求められるトルクと発熱のトレードオフで調節すると思われる。
ステッピングモータ 制御コー ド
以下のような機能を実現する簡単な制御コードを書いた。要TimerOne。
- パルスの開始・停止
- パルス幅の切り替え
- カレントダウンの切り替え
- 回転方向の切り替え
SteppingMotor.ino
として新規タブに貼り付ける。
RPM_PER_PULSE_KHZ
は特性図を見ると、パルス速度 1 kHzのとき回転速度 300 r/minとあるので300を使う。
// ステッピングモータ 制御コード#include <TimerOne.h>// ピンはどこでもOK#define PIN_MTR_DIR 5#define PIN_MTR_CK 6#define PIN_MTR_CD 7// 特性図// https://www.orientalmotor.co.jp/file_addon/products/st/image/tj_pkp214u06[].gif#define RPM_PER_PULSE_KHZ 300bool pulseIsHigh = false;void initSteppingMotor() {pinMode(PIN_MTR_DIR, OUTPUT);pinMode(PIN_MTR_CK, OUTPUT);pinMode(PIN_MTR_CD, OUTPUT);digitalWrite(PIN_MTR_DIR, LOW);digitalWrite(PIN_MTR_CK, LOW);digitalWrite(PIN_MTR_CD, HIGH);Timer1.initialize(1000*1000);}void stopPulse() {Timer1.detachInterrupt();}void startPulse() {Timer1.attachInterrupt(switchPulse);}void switchPulse() {pulseIsHigh = !pulseIsHigh;digitalWrite(PIN_MTR_CK, pulseIsHigh ? HIGH : LOW);}void setPulseLengthMillis(float pulseMillis) {int pulseMicros = pulseMillis * 1000;if (pulseMicros < 1) pulseMicros = 1;Timer1.setPeriod(pulseMicros);}void setPulseLengthByKHz(float khz) {float pulseMillis = 1.0 / khz;setPulseLengthMillis(pulseMillis);}void setPulseLengthByRPM(float rpm) {float pulseKHz = rpm / RPM_PER_PULSE_KHZ;setPulseLengthByKHz(pulseKHz);}void disableCurrentDown() {digitalWrite(PIN_MTR_CD, LOW);}void enableCurrentDown() {digitalWrite(PIN_MTR_CD, HIGH);}void setCW() {digitalWrite(PIN_MTR_DIR, LOW);}void setCCW() {digitalWrite(PIN_MTR_DIR, HIGH);}
ステッピングモータ 実行コード
以下のようにテスト用の実行コードを作った。 30rpm、300rpm、1000rpm、300rpm、30rpm、停止、のように回転し、一巡するごとに回転方向を反転する(あまり急激に変化させると脱調してしまい回らない)。
// ステッピングモータ 実行コードint prevPhase = -1;bool isClockwise = true;void setup() {Serial.begin(115200);initSteppingMotor();}void loop() {long now = millis();int interval = 1000;int phase = (now / interval) % 6;if (phase != prevPhase) {Serial.print("[");Serial.print(now);Serial.print("] ");if (isClockwise) Serial.print("(C) ");else Serial.print("(A) ");if (phase == 0) {Serial.println("stop");stopPulse();enableCurrentDown();}else if (phase == 1) {Serial.println("30rpm");disableCurrentDown();if (isClockwise) setCW();else setCCW();isClockwise = !isClockwise;setPulseLengthByRPM(30);startPulse();}else if (phase == 2) {Serial.println("300rpm");setPulseLengthByRPM(300);}else if (phase == 3) {Serial.println("1000rpm");setPulseLengthByRPM(1000);}else if (phase == 4) {Serial.println("300rpm");setPulseLengthByRPM(300);}else if (phase == 5) {Serial.println("30rpm");setPulseLengthByRPM(30);}else {Serial.println("nope");}}prevPhase = phase;}
ロータリエンコーダによる角度取得
ロ ータリエンコーダ 配線
ロータリエンコーダ本体からは、左から白・橙・黄・青・緑・茶・赤・黒の8本のケーブルが出ている。 これは説明書を見ると 赤・茶がA相(A+・A-)、 緑・青がB相(B+・B-)、 黃・橙がZ相(Z+、Z-)、 白がVCC(5V)、 黒がGNDになっている。 +-はそれぞれ出力が反転しているだけなので、 各相1本だけを接続すればいい。 Z+は軸が一周するタイミングで正になる端子で、誤差修正に使う。 よって、赤・緑・黃・白・黒の5本を接続する。
ロータリエンコーダの読み取りには割り込みを使うので、 A+、B+は2番、3番ピンに接続する。 Z+は4番ピン(これはどこでもOK)に接続することにする。 VCCとGNDはArduinoの5V、GNDに接続すればいい。
ロータリエンコーダ 計算コード
この記事を参考にして、ロータリエンコーダの角度計算をするコードを書いた。 A相B相 のパターンに基づく計算はそのままで、不要な部分の除去とZ相によるリセット、オーバーフロー防止のためカウント超過時のリセットを追加した。 なおPPRは200なので、1.8°の分解能になる。
RotaryEncoder.ino
として新規タブに貼り付ける。
// ロータリエンコーダ 計算コード#define PPR 200 // p/r, pulse per revolution#define PIN_RTE_A 2#define PIN_RTE_B 3#define PIN_RTE_Z 4#define NONE 0#define CW 1#define CCW 2volatile char direction = NONE; // NONE, CW, CCWvolatile int steps = 0;volatile bool prevPinA = false;volatile bool prevPinB = false;volatile bool prevPinZ = false;void initRotaryEncoder() {pinMode(PIN_RTE_A, INPUT_PULLUP);pinMode(PIN_RTE_B, INPUT_PULLUP);pinMode(PIN_RTE_Z, INPUT_PULLUP);attachInterrupt(0, onRotaryEncoderPulse, CHANGE); // pin2attachInterrupt(1, onRotaryEncoderPulse, CHANGE); // pin3}int getRotaryEncoderStep() {return steps;}float getRotaryEncoderAngleRate() {return (float)steps / PPR;}float getRotaryEncoderAngleDegrees() {return getRotaryEncoderAngleRate() * 360.0f;}float getRotaryEncoderAngleRadians() {return getRotaryEncoderAngleRate() * 2 * PI;}void onRotaryEncoderPulse() {bool pinZ = digitalRead(PIN_RTE_Z);if (pinZ && prevPinZ) steps = 0;bool pinA = !digitalRead(PIN_RTE_A);bool pinB = !digitalRead(PIN_RTE_B);if (pinA != prevPinA || pinB != prevPinB) {if (direction == NONE) {if (pinA) direction = CW;if (pinB) direction = CCW;}else {if (!pinA && !pinB) {if (direction == CW && prevPinB) steps++;else if (direction == CCW && prevPinA) steps--;if (steps < 0) steps += PPR;if (PPR <= steps) steps -= PPR;direction = NONE;}}}prevPinA = pinA;prevPinB = pinB;prevPinZ = pinZ;}
ロータリエンコーダ 実行コード
以下のようにテスト用の実行コードを作った。 感電に注意しつつ、手で軸を回して角度が変化することを確認する。
// ロータリエンコーダ 実行コードvoid setup() {Serial.begin(115200);initRotaryEncoder();}void loop() {Serial.println(getRotaryEncoderAngleDegrees());delay(10);}
ステッピングモータ+ロータリエンコーダ
シリアル通信で送られてきたJSONをパースする
シリアル通信で改行文字で区切られたJSONをやり取りするため、SerialJsonLineReader.ino
を作った。要ArduinoJson。
// Requirements:// #include <ArduinoJson.h>String serialBuffer = "";bool nextSerialLine(String *serialLine) {*serialLine = "";while (Serial.available() > 0) {int ch = Serial.read();if (ch != -1) {if (ch == '\n') {*serialLine = serialBuffer;serialBuffer = "";return true;}serialBuffer += (char) ch;}}return false;}bool nextSerialJson(JsonDocument *serialJson, bool *jsonError) {String serialLine = "";*jsonError = false;serialJson->clear();if (nextSerialLine(&serialLine)) {DeserializationError error;error = deserializeJson(*serialJson, serialLine);if (error == DeserializationError::Ok) {return true;}*jsonError = true;serialJson->clear();}return false;}
シリアル通信からステッピングモータ制御+角度取得
{"rpm": 300}
このようなJSONをシリアル通信でArduinoに送ることで、
ステッピングモータが回転するようにし、
また逐次回転角度をシリアル通信で送る実行コードを作った。
要SteppingMotor.ino
、
RotaryEncoder.ino
、
SerialJsonLineReader.ino
。
#include <ArduinoJson.h>#define MIN_RPM 30#define MAX_RPM 3000float rpm = 0;void setup() {Serial.begin(115200);initRotaryEncoder();initSteppingMotor();}void loop() {float angleDegrees = getRotaryEncoderAngleDegrees();if (rpm < MIN_RPM) {rpm = 0;stopPulse();enableCurrentDown();}else {setPulseLengthByRPM(rpm);startPulse();}StaticJsonDocument<255> msg;msg["angle"] = angleDegrees;msg["rpm"] = rpm;serializeJson(msg, Serial); Serial.println();// serialEvent is now deprecated and not working on some Arduino devices (e.g. Arduino Nano Every).// https://github.com/arduino/ArduinoCore-avr/issues/206#issuecomment-532133626// https://forum.arduino.cc/t/nano-every-serialevent-does-not-get-called/690025//// }//// void serialEvent() {// StaticJsonDocument<255> msg;bool jsonError = false;if (nextSerialJson(&msg, &jsonError)) {float _rpm = msg["rpm"];if (_rpm < MIN_RPM) _rpm = 0; // current downif (MAX_RPM < _rpm) _rpm = MAX_RPM;rpm = _rpm;}else if (jsonError) {// StaticJsonDocument<255> response;// serializeJson(response, Serial); Serial.println();}}