在运行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
- Thread Separation: All motor control logic lives in the
MotorWorkerthread, so it never blocks the main GUI thread. - Signal-Slot Communication: We use
pyqtSignalto 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. - Responsive Thread: Instead of
time.sleep(), we useQThread.msleep()which lets the thread check for stop signals while sleeping. - Safe Cleanup: The
cleanup_resourcesmethod 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

