こんにちは!今回は「モノづくりのハードルを下げる」をテーマに、Arduinoを使った電子工作の記録をシェアします。
「ダイヤルを回した分だけ、正確にモーターを動かしたい」
そんな時に役立つのが、ロータリーエンコーダーとステッピングモータの組み合わせです。初心者の方でも再現しやすいよう、構成を整理してみました。

今回作るものの概要
ダイヤル(エンコーダー)をカチカチと回すと、その動きに同期してステッピングモータが回転する仕組みを作ります。
使用部品
-
Arduino UNO(制御の心臓部)
-
ステッピングモータ (28BYJ-48)
-
ドライバ基板 (ULN2003)(モーターを力強く動かすため)
-
ロータリーエンコーダー(今回の操作部)
-
電源モジュール / 9V電池
-
ブレッドボード・ジャンパー線
このブログでは ELEGOO(エレゴー)製の Arduino 互換品を使って実験しています。Arduino互換品と聞くと品質が心配になる方もいるかもしれませんが、ELEGOO の製品は「互換品とは思えない品質」 で、Arduino公式ボードと同じように問題なく使えます。それでいて 価格はかなりリーズナブル なのが魅力です。
特にスターターキットは、
- Arduino互換ボード
- モータ(DC,サーボモータ,ステッピングモータ)
- 各種センサ
- 電源モジュール
- ブレッドボード
- ジャンパーワイヤ
といった 電子工作で必要なパーツが一式そろっており、初めてでもすぐに実験を始められる 便利なセットになっています。
ロータリーエンコーダーって何?
ロータリーエンコーダの仕組み(原理)
最も一般的な「光学式」を例に説明します。
内部には、細かいスリット(溝)が入った円盤が入っています。この円盤が回転することで、光を「通す・遮る」を繰り返し、それをセンサーが読み取ってデジタル信号(パルス)に変換します。
-
発光素子(LED)から光を出す。
-
回転するスリット入りの円盤を光が通る。
-
反対側の受光素子が、光の点滅を検知する。
-
点滅の回数を数えることで「どれくらい動いたか」を、点滅の速さで「どれくらいの速度か」を判断する。
大きく分けて2つのタイプ
ロータリーエンコーダには、信号の出し方によって「インクリメンタル」と「アブソリュート」の2種類があります。ここが一番のポイントです。
① インクリメンタル形(相対角)
回転した「量」を測るタイプです。
-
特徴:
回転すると「ピ・ピ・ピ」とパルス信号を出します。その数をカウントして移動量を計算します。 -
メリット:
構造がシンプルで安価。 -
デメリット:
電源を切ると、それまでどこにいたか忘れてしまいます(原点復帰が必要)。
② アブソリュート形(絶対角)
回転した「位置(角度)」を測るタイプです。
-
特徴:
回転角ごとに固有のコード(住所のようなもの)が割り振られています。 -
メリット:
電源を切っても、再起動した瞬間に「今どこにいるか」がすぐに分かります。 -
デメリット:
構造が複雑で、価格が高め。
| 特徴 | インクリメンタル形 | アブソリュート形 |
| 位置の把握 | 前の位置からの「変化量」 | 常に「絶対的な位置」 |
| 停電時 | 現在地を忘れる | 現在地を保持する |
| コスト | 低い(リーズナブル) | 高い |
| 主な用途 | 一般的なモーター制御、つまみ | 工作機械、ロボットアーム |
回路と接続
配線は少し複雑に見えますが、一つずつ繋げば大丈夫です。
【回路図】

プログラム(コード)
以下のコードをArduino IDEに書き込みます。
#include "Stepper.h"
#define STEPS 32 // 内部シャフト1回転あたりのステップ数
// 外部シャフト1回転あたり2048ステップ
volatile boolean TurnDetected; // 割り込みにはvolatileが必要
volatile boolean rotationdirection; // 時計回りまたは反時計回りの回転
const int PinCLK=2; // CLK信号を使用して割り込みを生成する
const int PinDT=3; // DT信号の読み取り
const int PinSW=4; // 読み取りプッシュボタンスイッチ
int RotaryPosition=0; // ステッピングモーターの位置を保存
int PrevPosition; // 前回の回転位置 精度を確認するための値
int StepsToTake; // ステッパーをどれくらい動かすか
// モータードライバーピンの適切なシーケンス設定
// In1、In2、In3、In4 のシーケンスを 1-3-2-4 で設定
Stepper small_stepper(STEPS, 8, 10, 9, 11);
// CLKがHIGHからLOWに変わると割り込みルーチンが実行される
void isr () {
delay(4); // 最終操作を待つ
if (digitalRead(PinCLK))
rotationdirection= digitalRead(PinDT);
else
rotationdirection= !digitalRead(PinDT);
TurnDetected = true;
}
void setup () {
pinMode(PinCLK,INPUT);
pinMode(PinDT,INPUT);
pinMode(PinSW,INPUT);
digitalWrite(PinSW, HIGH); // スイッチ用プルアップ抵抗
attachInterrupt (0,isr,FALLING); // 割り込み0は常にArduino UNOのピン2に接続されます
}
void loop () {
small_stepper.setSpeed(700); //回転速度
if (!(digitalRead(PinSW))) { // ボタンが押されているかどうかを確認する
if (RotaryPosition == 0) { // ボタンがすでに押されているかどうかを確認する
} else {
small_stepper.step(-(RotaryPosition*50));
RotaryPosition=0; // 位置をゼロにリセット
}
}
// 回転が検出された場合実行
if (TurnDetected) {
PrevPosition = RotaryPosition; // 前の位置を変数に保存する
if (rotationdirection) {
RotaryPosition=RotaryPosition-1;} // 位置を1つ下げる
else {
RotaryPosition=RotaryPosition+1;} // ポジションを1つ増やす
TurnDetected = false; // 新しい回転が検出されるまでIFループを繰り返さない
// ステッピングモーターをどの方向に動かすか
if ((PrevPosition + 1) == RotaryPosition) { // モーターを時計回りに動かす
StepsToTake=50;
small_stepper.step(StepsToTake);
}
if ((RotaryPosition + 1) == PrevPosition) { // モーターを反時計回りに動かす
StepsToTake=-50;
small_stepper.step(StepsToTake);
}
}
digitalWrite(8, LOW);
digitalWrite(9, LOW);
digitalWrite(10, LOW);
digitalWrite(11, LOW);
}
コード解説
全体のプログラム動作概要
このプログラムの主な目的は、「ロータリーエンコーダーの回転を検知し、その動きに合わせてステッピングモーターを回転させること」です。
具体的には以下のような動作を行います:
-
回転の検知:
ダイヤルの「1カチ(1クリック)」を、Arduinoの割り込み機能で瞬時に捉えます。 -
方向の判別:
2つの信号のズレ(位相差)を利用して、時計回りか反時計回りかを判断します。 -
モーターの駆動:
ダイヤルが1クリック動くごとに、モーターを50ステップ分動かします。 -
リセット機能:
ダイヤル自体がボタンになっており、押し込むと電源を入れた時の「0の位置」まで自動で戻ります。
プログラムコードのポイント
このコードを理解する上で、特に重要な「肝」となる部分が3つあります。
-
割り込み(Interrupt)の活用:
通常のプログラムは上から順に処理を行いますが、ダイヤルを回すのは人間のタイミングです。
attachInterruptを使うことで、他の処理をしていても「回った瞬間」だけは最優先で処理するため、取りこぼしがありません。 -
位相差による方向判定:
CLKピンとDTピンの2つの信号は、わざとタイミングがズレて出力されます。CLKが変化した瞬間のDTの状態を見れば、右か左かが100%分かります。 -
チャタリング対策(Debouncing):
スイッチを押した際、物理的な接点が微細に震えることで「カチカチカチッ」と複数回押されたと誤認されることがあります。これを防ぐために、数ミリ秒の待機時間(
delay(4))を設けています。 -
モーターの「熱」対策:
ステッピングモーターは、止まっている時も電気を流し続けると非常に熱くなります。このコードでは、動かした直後に全てのピンをLOW(0V)にすることで、無駄な発熱を抑える工夫がされています。
各行のプログラムコードの意味
コードを機能ブロックごとに分けて解説します。
設定と準備(ライブラリと変数)
#include "Stepper.h" // モーター制御用のライブラリを読み込み
#define STEPS 32 // モーター内部の1回転ステップ数
volatile boolean TurnDetected; // 回転を検知したかどうかのフラグ
volatile boolean rotationdirection; // 回転方向(時計・反時計)
-
volatileは「割り込みの中で書き換わる変数ですよ」とコンピュータに教える魔法の言葉です。
割り込み処理(isr関数)
void isr () {
delay(4); // チャタリング防止
if (digitalRead(PinCLK))
rotationdirection = digitalRead(PinDT); // 方向を判定
else
rotationdirection = !digitalRead(PinDT);
TurnDetected = true; // 回転したことを記録
}
-
ダイヤルが動いた瞬間に呼び出されます。ここで「どっちに回ったか」を判断し、「回ったよ!」という印(
TurnDetected)を立てます。
メインループ(loop関数)
small_stepper.setSpeed(700); // モーターの速度を設定
// ボタンが押されたら0の位置に戻る
if (!(digitalRead(PinSW))) {
if (RotaryPosition != 0) {
small_stepper.step(-(RotaryPosition * 50)); // 逆回転して戻る
RotaryPosition = 0; // 位置をリセット
}
}
// 回転が検知されたらモーターを動かす
if (TurnDetected) {
if (rotationdirection) {
RotaryPosition--; // 位置を減らす
small_stepper.step(-50); // 反時計回りに50ステップ
} else {
RotaryPosition++; // 位置を増やす
small_stepper.step(50); // 時計回りに50ステップ
}
TurnDetected = false; // 処理が終わったのでフラグを戻す
}
// 待機中にモーターへの電流を止める(発熱防止)
digitalWrite(8, LOW); digitalWrite(9, LOW);
digitalWrite(10, LOW); digitalWrite(11, LOW);
}
-
常に「ボタンは押されたか?」「ダイヤルは回ったか?」を確認しています。
-
最後に各ピンを
LOWにするのは、使っていない時にモーターが熱くなるのを防ぐための工夫です。
使用されている関数の意味
コード内で使われている主要な関数について解説します。
| 関数 | 役割 | 補足説明 |
attachInterrupt() |
割り込みの予約 | 特定のピン(Pin 2)が変化した時だけ isr を呼び出します。 |
digitalRead() |
ピンの状態確認 | 電圧が 5V(HIGH) か 0V(LOW) かを調べます。 |
small_stepper.step() |
モーターを動かす | 正の数で時計回り、負の数で反時計回りに指定数だけ進みます。 |
setSpeed(700) |
モーターの速さ設定 | 1分間あたりの回転数(RPM)を指定します。 |
実験結果:動かしてみた感想
想定通り、エンコーダーのカチカチという手応えに合わせて、モーターが小気味よく動いてくれました!
ただ、面白い挙動(課題)も見つかりました。
今のコードだと、100回カチカチと回した後にリセットボタンを押すと、モーターが 「ギュイーン!」 と一気に 100 ×times 50ステップ(5000ステップ)逆回転して戻ろうとします。
-
速度の調整: 戻る速度が遅すぎると待ち時間が発生します。
-
脱調に注意: 逆に速すぎると「ガガガッ」と異音がしてモーターがついてこれなくなる(脱調)ため、
setSpeedの調整が重要です。
まとめ・応用
今回の仕組みを応用すれば、以下のようなものも作れそうです。
-
自作スライダー: カメラの台を数ミリ単位で動かす。
-
遠隔ドアロック: 手元のダイヤルで鍵を回す。
電子工作は、一度動くものができると一気に楽しくなりますね。「自分もやってみたい」という方の助けになれば幸いです!