You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在运行PyQt5 GUI时控制Raspberry Pi直流电机转速的问题

Hey there! I totally get where you're coming from—mixing blocking calls like time.sleep() with PyQt GUIs is a classic pitfall because the main thread handles all UI updates and event processing. Let's break down how to fix this cleanly with Qt's built-in threading tools, which play nicely with PyQt's event loop.


Why Your GUI Freezes

PyQt runs all user interactions and UI refreshes on the main thread. When you call time.sleep(x) directly in a GUI callback (like a button click), you block this thread completely. The event loop can't process slider adjustments, button presses, or screen updates until the sleep finishes—hence the frozen GUI.

The Solution: Offload Motor Control to a QThread

Qt's QThread is designed for this exact scenario: it lets you run long-running/blocking tasks in a separate thread while keeping the main thread free for the GUI. Here's a complete, tailored implementation for your Raspberry Pi motor control project:

Step 1: Full Code Implementation

from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QPushButton, QSlider,
    QVBoxLayout, QWidget, QStatusBar
)
from PyQt5.QtCore import QThread, pyqtSignal, Qt
import RPi.GPIO as GPIO

# Configure your motor driver pins (adjust to match your setup)
IN1 = 17
IN2 = 27
ENA = 18

class MotorWorker(QThread):
    # Signal to send status updates back to the GUI thread
    status_update = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.is_running = False
        self.target_speed = 50  # Default speed (0-100)
        
        # Initialize GPIO in the main thread (safe for one-time setup)
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(IN1, GPIO.OUT)
        GPIO.setup(IN2, GPIO.OUT)
        GPIO.setup(ENA, GPIO.OUT)
        self.pwm = GPIO.PWM(ENA, 1000)  # 1kHz PWM frequency
        self.pwm.start(0)

    def set_speed(self, speed):
        # Update speed safely (thread-safe since it's a simple assignment)
        self.target_speed = speed
        self.pwm.ChangeDutyCycle(self.target_speed)

    def run(self):
        # This method runs in the separate thread
        self.is_running = True
        self.status_update.emit("Motor running")
        
        # Set motor direction (adjust IN1/IN2 for reverse)
        GPIO.output(IN1, GPIO.HIGH)
        GPIO.output(IN2, GPIO.LOW)

        # Use QThread's msleep instead of time.sleep to keep the thread responsive
        while self.is_running:
            self.msleep(100)  # Small sleep to reduce CPU usage

        # Stop motor when thread exits
        GPIO.output(IN1, GPIO.LOW)
        GPIO.output(IN2, GPIO.LOW)
        self.pwm.stop()
        self.status_update.emit("Motor stopped")

    def stop_motor(self):
        # Signal the thread to exit gracefully
        self.is_running = False

class MotorControlGUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("RPi Motor Controller")
        self.setGeometry(100, 100, 350, 200)
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)

        # Create GUI controls
        self.start_btn = QPushButton("Start Motor")
        self.stop_btn = QPushButton("Stop Motor")
        self.speed_slider = QSlider(Qt.Horizontal)
        self.speed_slider.setRange(0, 100)
        self.speed_slider.setValue(50)

        # Layout setup
        central_widget = QWidget()
        layout = QVBoxLayout(central_widget)
        layout.addWidget(self.start_btn)
        layout.addWidget(self.stop_btn)
        layout.addWidget(self.speed_slider)
        self.setCentralWidget(central_widget)

        # Initialize motor worker thread
        self.motor_worker = MotorWorker()

        # Connect signals and slots
        self.start_btn.clicked.connect(self.start_motor)
        self.stop_btn.clicked.connect(self.stop_motor)
        self.speed_slider.valueChanged.connect(self.update_speed)
        self.motor_worker.status_update.connect(self.update_status)

        # Cleanup on window close
        self.destroyed.connect(self.cleanup_resources)

    def start_motor(self):
        if not self.motor_worker.isRunning():
            self.motor_worker.start()

    def stop_motor(self):
        if self.motor_worker.isRunning():
            self.motor_worker.stop_motor()

    def update_speed(self, speed):
        self.motor_worker.set_speed(speed)
        self.statusBar.showMessage(f"Speed set to {speed}%")

    def update_status(self, status):
        self.statusBar.showMessage(status)

    def cleanup_resources(self):
        # Ensure thread exits and GPIO is cleaned up
        self.stop_motor()
        self.motor_worker.wait()  # Wait for thread to finish
        GPIO.cleanup()

if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    gui = MotorControlGUI()
    gui.show()
    sys.exit(app.exec_())

Key Things to Note

  1. Thread Separation: All motor control logic lives in the MotorWorker thread, so it never blocks the main GUI thread.
  2. Signal-Slot Communication: We use pyqtSignal to send status updates from the worker thread to the GUI—this is the only safe way to update UI elements from a non-main thread.
  3. Responsive Thread: Instead of time.sleep(), we use QThread.msleep() which lets the thread check for stop signals while sleeping.
  4. Safe Cleanup: The cleanup_resources method ensures the motor stops and GPIO is properly reset when the window closes.

Alternative: Using Python's threading Module

If you prefer standard Python threading, you can use it too—just make sure to avoid direct GUI modifications. The core idea is the same: offload the motor loop to a separate thread and use thread-safe communication (like queues or Qt signals) to interact with the GUI.

However, QThread is generally preferred for PyQt projects because it integrates seamlessly with Qt's event loop and signal-slot system.

内容的提问来源于stack exchange,提问作者Benx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 12:35:24