PR

Pythonのthreadingでステッピングモーターを2軸同時制御【XYテーブル自作 第6回】

RaspberryPi

XYテーブル製作シリーズ 第6回

スポンサーリンク

はじめに:X軸とY軸を「同時に」動かして斜め移動する

前回まではX軸単体・Y軸単体を動かしてきました。XYテーブルとして図形を描いたり位置決めするには、XとYを同時に動かして斜め移動する必要があります。今回はPythonの threading(スレッド)で2つのモーターを同時に動作させる方法を解説します。

この記事で分かること

  • threading.Thread でX軸スレッド・Y軸スレッドを同時に起動する仕組み
  • 軸ごとにピンをまとめて渡し、加減速付きで動かす run_motor()
  • X軸50mm・Y軸30mm を同時に動かして斜め移動を実現するフルコード
  • A4988ドライバー2台のGPIOピン配置と結線図

スポンサーリンク

ハードウェア構成とGPIOピン設定

部品型番
マイコンRaspberry Pi 3 Model A+
モータードライバ ×2A4988
モーター ×2NEMA17
電源12V 5A

GPIOピン配置

# X軸
X_STEP = 21   X_DIR = 20   X_EN = 16
X_MS1 = 17    X_MS2 = 27   X_MS3 = 22
X_RESET = 5   X_SLEEP = 6

# Y軸
Y_STEP = 2    Y_DIR = 26   Y_EN = 18
Y_MS1 = 23    Y_MS2 = 24   Y_MS3 = 25
Y_RESET = 12  Y_SLEEP = 13
Raspberry PiにA4988ドライバー2台を接続しX軸・Y軸のステッピングモーターを制御する結線図
X軸・Y軸それぞれにA4988を1台ずつ

結線であると便利なもの:


スポンサーリンク

Pythonのthreadingで2軸を同時制御する

Pythonの threading を使うと複数の処理を同時に動かせます。今回のプログラムではX軸スレッドとY軸スレッドを同時に起動し、tx.join() / ty.join() で両方の完了を待ちます。run_motor() には第5回の加減速(台形駆動)も組み込んでいます。

import RPi.GPIO as GPIO
import time
import threading

GPIO.setmode(GPIO.BCM)

# -------------------------
# X軸
# -------------------------
X_STEP = 21
X_DIR  = 20
X_EN   = 16
X_MS1  = 17
X_MS2  = 27
X_MS3  = 22
X_RESET = 5
X_SLEEP = 6

# -------------------------
# Y軸
# -------------------------
Y_STEP = 2
Y_DIR  = 26
Y_EN   = 18
Y_MS1  = 23
Y_MS2  = 24
Y_MS3  = 25
Y_RESET = 12
Y_SLEEP = 13

# -------------------------
# モーション設定
# -------------------------
BELT_PITCH = 2.0
PULLEY_TEETH = 32

MM_PER_REV = BELT_PITCH * PULLEY_TEETH

FULL_STEPS = 200
MICROSTEP = 16

STEPS_PER_REV = FULL_STEPS * MICROSTEP
STEPS_PER_MM = STEPS_PER_REV / MM_PER_REV

ACC_DEC_STEPS = 100


# -------------------------
# GPIO初期化
# -------------------------
def gpio_init_axis(STEP, DIR, EN, MS1, MS2, MS3, RESET, SLEEP):

    pins = [STEP, DIR, EN, MS1, MS2, MS3, RESET, SLEEP]

    for p in pins:
        GPIO.setup(p, GPIO.OUT)

    GPIO.output(RESET, GPIO.HIGH)
    GPIO.output(SLEEP, GPIO.HIGH)

    GPIO.output(MS1, GPIO.HIGH)
    GPIO.output(MS2, GPIO.HIGH)
    GPIO.output(MS3, GPIO.HIGH)

    GPIO.output(EN, GPIO.LOW)


# -------------------------
# 加減速計算
# -------------------------
def get_intervals(total_steps, acc_steps, total_time):

    const_steps = max(total_steps - acc_steps*2, 1)

    total_ratio = acc_steps*0.5 + const_steps + acc_steps*0.5

    base_interval = total_time / total_ratio / 2

    intervals = []

    for i in range(acc_steps):
        intervals.append(base_interval*(acc_steps-i)/acc_steps)

    for i in range(const_steps):
        intervals.append(base_interval)

    for i in range(acc_steps):
        intervals.append(base_interval*(i+1)/acc_steps)

    return intervals


# -------------------------
# モーター制御
# -------------------------
def run_motor(axis_name,
              STEP, DIR, EN,
              MS1, MS2, MS3,
              RESET, SLEEP,
              distance_mm, cycles):

    print(axis_name,"開始")

    gpio_init_axis(STEP,DIR,EN,MS1,MS2,MS3,RESET,SLEEP)

    steps = int(distance_mm * STEPS_PER_MM)

    intervals = get_intervals(steps, ACC_DEC_STEPS, 1.5)    # 1.5秒で加減速込みの移動を完了させる

    for c in range(cycles):

        # 正転
        if axis_name == "X軸":
            GPIO.output(DIR,GPIO.LOW)
        else:
            GPIO.output(DIR,GPIO.HIGH)

        for t in intervals:

            GPIO.output(STEP,GPIO.HIGH)
            time.sleep(t)

            GPIO.output(STEP,GPIO.LOW)
            time.sleep(t)

        # 逆転
        if axis_name == "X軸":
            GPIO.output(DIR,GPIO.HIGH)
        else:
            GPIO.output(DIR,GPIO.LOW)

        for t in intervals:

            GPIO.output(STEP,GPIO.HIGH)
            time.sleep(t)

            GPIO.output(STEP,GPIO.LOW)
            time.sleep(t)

    GPIO.output(EN,GPIO.HIGH)

    print(axis_name,"終了")


# -------------------------
# XY同時スタート
# -------------------------
def start_both():

    x_dist = 60 # 移動距離(mm)
    x_cycle = 1 # 移動回数

    y_dist = 30 # 移動距離(mm)#
    y_cycle = 2 # 移動回数

    tx = threading.Thread(
        target=run_motor,
        args=("X軸",
              X_STEP, X_DIR, X_EN,
              X_MS1, X_MS2, X_MS3,
              X_RESET, X_SLEEP,
              x_dist, x_cycle)
    )

    ty = threading.Thread(
        target=run_motor,
        args=("Y軸",
              Y_STEP, Y_DIR, Y_EN,
              Y_MS1, Y_MS2, Y_MS3,
              Y_RESET, Y_SLEEP,
              y_dist, y_cycle)
    )

    tx.start()
    ty.start()

    tx.join()
    ty.join()


# -------------------------
# 実行
# -------------------------
try:

    start_both()

finally:

    GPIO.cleanup()
python3 XY_both_move.py

X軸とY軸が同時に動作し、斜め移動が実現できます。



スポンサーリンク

よくある質問(FAQ)

Qthreading を使うと本当に「同時」に動きますか?

AOSレベルではX軸・Y軸のスレッドが並行して実行され、体感上は同時に動きます。ただしPythonのGIL(グローバルインタプリタロック)の影響で厳密な同時刻ではありません。ステップパルスの間隔が μ秒単位でずれることはありますが、XYテーブルの位置決め用途では実用上問題ありません。

Q斜め移動で直線がガタつきます(きれいな斜線にならない)。

AX軸とY軸を独立に動かすと、移動距離の短い軸が先に止まり「L字」に近い軌跡になります。きれいな斜線には、両軸の移動時間を合わせる(速い軸を遅くする)か、1ループ内で両軸のパルスをBresenhamアルゴリズムで配分する直線補間が必要です。

Q片方のモーターだけ動きません/逆に回ります。

AGPIOピンの割り当てミス、EN ピンが HIGH のまま(LOWで有効)、コイル配線の相違いが主な原因です。逆回転は run_motor 内で軸名により DIR を切り替えるか、配線でコイルのペアを入れ替えます。

Qスレッド実行後にプログラムが終了しません。

Atx.join() / ty.join() で両スレッドの完了を待ってから finally の GPIO.cleanup() に進みます。daemon=True にしたスレッドはメインが終わると強制終了されるので、確実に動かし切りたい処理では join() を使います。


スポンサーリンク

XYテーブル製作シリーズ(全12回)

補足記事

第1.5回・補足 A4988の電流制限(Vref)調整方法|焼損を防ぐ計算式とテスターでの設定手順 モーターを回す前に必ずやる A4988 の電流調整。 🔀 番外編 L6470が動かない原因|A4988との違いとSPI通信・デイジーチェーンの罠 高機能ドライバ L6470 を A4988 と同じ感覚で使ってハマった話。 🗺️ 全体像 自作XYテーブルの全手順ロードマップ|Raspberry Pi+ステッピングモーターで2軸制御 第0回〜第11回の全工程をまとめたロードマップ。

ニンジン🥕アイコン

Follow me on X

本ブログの中の人「ニンジン🥕」はXやってます。新着記事はいち早くポストでお知らせしているので、見逃したくない方は @ninjin_py_vba をフォローしてみてください🥕

@ninjin_py_vba をフォロー

この記事が役に立ったら、シェアしてもらえると嬉しいです🥕

Xでポスト はてブ LINE Pocket

PROJECT LOG — SERIES INDEX
スポンサーリンク

シリーズ記事一覧

電子工作・自動化ツールの製作記録をシリーズごとにまとめています。気になるテーマからお読みください。


超音波距離センサーHC-SR04をRaspberry Pi Picoで使う|仕組み・配線・精度検証
.uds1-block { position: relative; overflow: hidden; border-radius: 12px; border: 1px solid #f0cba8; background: linear-g…
今週の注目ガジェット〜09/07週〜|電子工作・自作向け新製品まとめ(TickrCast/Radxa Linkr ほか)
ニンジンのガジェット通信 09/07週。ESP32-P4の自作気象モニター、極小IP-KVM「Radxa Linkr」、ESP32のDINレール型PLC、LTE開発キットなど、電子工作で使えそうな新製品5つを解説。
サーボモータの角度をタクトスイッチで可変|OLED(SSD1306)に現在角度をリアルタイム表示
Raspberry Pi Pico 2W×MG996RにタクトスイッチとOLED(SSD1306)を追加。ボタンで角度を可変しながら現在角度をリアルタイム表示するMicroPython実装を、framebufの文字拡大やサーボの遊び対策まで実機ベースで解説します。
MG996RサーボモーターをRaspberry Pi Pico 2Wで動かす:PWM制御とキャリブレーション実践
MG996RサーボモーターをRaspberry Pi Pico 2Wで動かす方法を解説。公称180度のはずが実測198度だった個体差の原因究明から、ホーンのズレ補正、タクトスイッチでの角度制御まで実機検証の様子を紹介します。
Tapo P110Mの消費電力をExcelレポート化する【年次集計・電気代試算・セキュリティ判断編】
FastAPI+openpyxlでTapo P110Mの消費電力をExcelレポート化。15秒間隔の生ログを日次集計する設計や、31円/kWhでの電気代試算、外部公開を見送った理由まで解説します。

コメント