PR

XYテーブルGUIに単軸動作・片道移動・安全な終了処理を追加【XYテーブル自作 第10回】

RaspberryPi

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

スポンサーリンク

はじめに:現場で欲しくなる4つの機能を追加する

第9回で原点復帰やソフトリミットを実装しました。実際に運用すると、さらに踏み込んだ機能が欲しくなります。「X軸だけ微調整したい」「往復ではなく決まった位置へ移動させたい」「今何mmの位置か一目で知りたい」「×ボタンで閉じたら裏でモーターが変な動きをした」——今回はこれらを解決し、実用的な制御アプリに仕上げます。

この記事で分かること

  • X軸だけ/Y軸だけ動かす単軸動作関数とGUIボタン
  • 往復ではなく指定距離だけ動いて止まる片道移動(位置決め)と、移動後の current_pos 更新
  • ステップ数から現在位置を計算し、% 50 で間引いてリアルタイム表示する
  • 右上の「×」ボタンを無効化し、GPIO解放を必ず通る安全な終了ボタン
単軸動作・片道移動・終了ボタンを追加した第10回のXYテーブル操作GUI
第10回で完成するGUI

スポンサーリンク

単軸動作の実装

「X軸だけ動かしてセンサー位置を確認する」といった単独操作は運用で頻出します。

def start_x_cycle():
    distance = float(entry_X_dist.get())
    cycles = int(entry_X_cycle.get())
    threading.Thread(
        target=run_motor,
        args=("X軸", X_STEP, X_DIR, X_EN, X_MS1, X_MS2, X_MS3,
              X_RESET, X_SLEEP, distance, cycles),
        daemon=True
    ).start()
# Y軸も同様(引数をY軸用に変更)
tk.Button(root, text="X軸サイクル実行", command=start_x_cycle, bg="lightblue")\
    .grid(row=10, column=0, pady=5)
tk.Button(root, text="Y軸サイクル実行", command=start_y_cycle, bg="lightblue")\
    .grid(row=10, column=1, pady=5)

スポンサーリンク

片道移動(位置決め)機能

「往復」ではなく「指定した距離だけ移動して止まる」機能です。ワークのセットや位置決めに欠かせません。第10回で追加した run_motor_oneway() のポイントは、移動後に current_pos を更新することです。

# ============================================
# 片道運動の関数(第10回追加)
# ============================================
def run_motor_oneway(axis_name, STEP, DIR, EN, MS1, MS2, MS3, RESET, SLEEP,
              distance_mm):
    
    global stop_flag,current_pos
    stop_flag = False
    
    start_pos =current_pos[axis_name]
    target_pos = start_pos + distance_mm
    
    # -----------------------------------------------
    # ソフトリミットチェック
    if axis_name == "X軸":
        if not (X_MIN_MM <= target_pos <= X_MAX_MM):
            message_queue.put(("error", f"{axis_name}移動範囲外:{target_pos:.2f}mm"))
            return
    else:
        if not (Y_MIN_MM <= target_pos <= Y_MAX_MM):
            message_queue.put(("error", f"{axis_name}移動範囲外:{target_pos:.2f}mm"))
            return
    
    steps = int(distance_mm * STEPS_PER_MM)
    if steps < 1:
        return
    intervales = get_intervals(steps,ACC_DEC_STEPS,1.5)
    gpio_init_axis(STEP,DIR,EN,MS1,MS2,MS3,RESET,SLEEP)
    
    step_count = 0  # ★ 現在位置(ステップ数)
    
    try:
        #-----正転のみ-----
        if axis_name =="X軸":
            GPIO.output(DIR,GPIO.LOW)
        else:
            GPIO.output(DIR,GPIO.HIGH)
        
        for t in intervales:
            if stop_flag:
                break
        
            GPIO.output(STEP,GPIO.HIGH)
            time.sleep(t)
            GPIO.output(STEP,GPIO.LOW)
            time.sleep(t)
            
            step_count += 1
            
            if step_count % 50 ==0:
                current_mm = step_count / STEPS_PER_MM
                message_queue.put(("pos",axis_name,current_mm))
        
        GPIO.output(EN,GPIO.HIGH)
        
        #★片道なので位置を更新
        current_pos[axis_name]=target_pos
        message_queue.put(("pos",axis_name,current_pos[axis_name]))
        message_queue.put(("info",f"{axis_name}片道移動完了"))
    
    finally:
        print(f"[{axis_name}]片道終了")
#片道運動の関数===================================
tk.Button(root, text="X軸 片道", command=start_x_oneway, bg="lightblue")\
    .grid(row=11, column=0, pady=5)
tk.Button(root, text="Y軸 片道", command=start_y_oneway, bg="lightblue")\
    .grid(row=11, column=1, pady=5)

スポンサーリンク

現在位置のリアルタイム表示

ステッピングモーターは送ったパルス数で移動距離が分かります。ステップを数えて位置を計算し、Queueで表示を更新します。

step_count += 1
if step_count % 50 == 0:                    # 50ステップごとに送る(間引き)
    current_mm = step_count / STEPS_PER_MM
    message_queue.put(("pos", axis_name, current_mm))

ポイント:すべてのステップでGUIを更新しようとすると通信過多でアプリがフリーズします。% 50(剰余演算)で適度に間引くのがスムーズな表示のコツです。

# GUI側(Queueから受信)
if msg[0] == "pos":
    _, axis, pos_mm = msg
    if axis == "X軸":
        x_pos_var.set(f"x:{pos_mm:.2f}mm")
    else:
        y_pos_var.set(f"y:{pos_mm:.2f}mm")

スポンサーリンク

安全なGUI終了(最重要)

装置制御では、右上の「×」ボタンでいきなり閉じるのは厳禁です。

なぜ「×」ボタンが危険なのか

  • GPIOの保持:強制終了されると出力ピンがHIGHのまま固定され、モーターに電流が流れ続けたり異音が出る
  • ゾンビスレッド:GUIは消えてもバックグラウンドのスレッドが生き残り、予期せぬ動作を続ける

対策:×ボタンの無効化と終了ボタン

root.protocol("WM_DELETE_WINDOW", disable_close)   # ×ボタンを無効化

def quit_program():
    global stop_flag
    stop_flag = True
    GPIO.output(X_EN, GPIO.HIGH)
    GPIO.output(Y_EN, GPIO.HIGH)
    root.destroy()

btn_quit = tk.Button(root, text="プログラム終了", command=quit_program,
                     bg="gray", fg="white")
btn_quit.grid(row=7, column=0, columnspan=2, pady=15)

これで「モーター停止 → GPIO解放 → GUI終了」という安全な終了ができます。


スポンサーリンク

まとめと次回予告

  • 単軸操作で細かな調整が可能に
  • 片道移動で正確な位置決めを実現
  • リアルタイム表示で動作状況を把握
  • 安全終了でハードウェアを保護

次回・第11回(完結編)では、これまでのコードを整理した完成版フルコードと、XYテーブルの完成構成・設計資料(図面・BOM)を公開します。

次に読む記事

🏁 次回はこちら(第11回・完結編) 自作XYテーブルの完成構成と設計資料(図面・BOM・結線図)|完成版Pythonコード配布 完成版コードと機械設計資料。 🏠 前回(第9回) XYテーブルGUIに原点復帰・現在位置表示・ソフトリミットを実装|Queueでスレッド安全にGUI更新 今回のベースになったGUI。 🗺️ 全体像 自作XYテーブルの全手順ロードマップ|Raspberry Pi+ステッピングモーターで2軸制御 第0回〜第11回の全工程をまとめたロードマップ。

スポンサーリンク

よくある質問(FAQ)

Q片道移動と往復サイクルはコード上どう違いますか?

A往復(run_motor)は正転ループ→逆転ループを cycles 回繰り返し、開始位置に戻ります。片道(run_motor_oneway)は正転ループのみで、移動後に current_pos[axis] = target_pos と位置を更新します。位置決めでは片道、動作確認では往復を使い分けます。

Q現在位置表示がカクカクします/飛びます。

Astep_count % 50 の 50 を大きくすると更新頻度が下がりカクつき、小さくすると通信過多でフリーズ気味になります。剰余の値と root.after の間隔(100ms程度)をセットで調整します。

Q×ボタンを無効化したらウィンドウが閉じられなくて困ります。

Aそれが狙いです。必ず「プログラム終了」ボタンから quit_program() を通し、stop_flag=True → GPIO.output(EN, HIGH) → root.destroy() の順で安全に終了させます。開発中に手早く閉じたいときだけ一時的に protocol の行をコメントアウトします。

Qプログラム終了後もモーターが少し熱いままです。

Aquit_program で EN を HIGH にすればコイル電流は切れますが、直前まで通電していた熱は残ります。異常な高温でなければ問題ありません。恒常的に熱いならA4988のVref(電流制限)が過大なので下げます。

Qdaemon=True のスレッドと通常スレッドの使い分けは?

A単発の動作(単軸サイクル・片道)は daemon=True にして、プログラム終了時に道連れで終わるようにします。確実に完了させたい処理は daemon を付けず join() で待ちます。緊急停止は stop_flag で協調的にキャンセルします。


スポンサーリンク

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での電気代試算、外部公開を見送った理由まで解説します。

コメント