使用tkinter的root.after()后旋转编码器值卡顿的技术咨询
root.after() Hey there! I’ve dealt with this exact issue before—Tkinter’s single-threaded event loop gets easily blocked, which is why your rotary encoder is stuttering and interrupts aren’t working right when you use root.after(). Let’s break down what’s wrong and fix it.
What’s Causing the Problem?
Tkinter runs everything in one main thread, including the root.mainloop() that handles GUI updates, timer callbacks, and your rotary encoder’s interrupt. Your original update() function uses time.sleep(0.5) twice—this completely freezes the event loop while it waits, so your encoder’s callback can’t fire until the sleep finishes. That’s the stutter you’re seeing!
The Fix: Ditch time.sleep() for Chained root.after() Calls
Instead of blocking the loop with sleep, we’ll use root.after() to schedule each update step one after another. This lets the event loop handle encoder interrupts and GUI events in between steps.
Here’s the revised code with key improvements:
import pigpio import tkinter as tk from PIL import ImageTk,Image import serial # Serial port initialization i = "*00T%" j = "*01T%" s = serial.Serial(port='/dev/ttyS0', baudrate=9600, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=1) class decoder: # Rotary encoder decoding class def __init__(self, pi, gpioA, gpioB, callback): self.pi = pi self.gpioA = gpioA self.gpioB = gpioB self.callback = callback self.levA = 0 self.levB = 0 self.lastGpio = None # Set up GPIO pins with pull-ups self.pi.set_mode(gpioA, pigpio.INPUT) self.pi.set_mode(gpioB, pigpio.INPUT) self.pi.set_pull_up_down(gpioA, pigpio.PUD_UP) self.pi.set_pull_up_down(gpioB, pigpio.PUD_UP) # Attach edge callbacks self.cbA = self.pi.callback(gpioA, pigpio.EITHER_EDGE, self._pulse) self.cbB = self.pi.callback(gpioB, pigpio.EITHER_EDGE, self._pulse) def _pulse(self, gpio, level, tick): # Update current pin levels if gpio == self.gpioA: self.levA = level else: self.levB = level # Determine rotation direction if gpio != self.lastGpio: self.lastGpio = gpio if gpio == self.gpioA and level == 1: if self.levB == 1: self.callback(1) elif gpio == self.gpioB and level == 1: if self.levA == 1: self.callback(-1) if __name__ == "__main__": rpm = 0 tor = 0 pow_val = 0 # Renamed to avoid conflict with built-in pow() function pi = pigpio.pi() st = 1 pos = 0 def callback(way): # Rotary encoder interrupt handler global pos, st if st == 1: pos += way pos = max(0, min(pos, 9999)) # Simplified boundary clamping var.set(pos) print(f"pos={pos}") def rpm_update(): global rpm s.write(str.encode(i)) print(i) if s.inWaiting(): rpm_data = s.readline(s.inWaiting()) rpm = rpm_data.decode('utf-8', errors='ignore') rpm = rpm[5:-1] print(rpm) var2.set(rpm) # Schedule torque update after 500ms root.after(500, tor_update) def tor_update(): global tor s.write(str.encode(j)) print(j) if s.inWaiting(): tor_data = s.readline(s.inWaiting()) tor = tor_data.decode('utf-8', errors='ignore') tor = tor[4:-1] print(tor) var3.set(tor) # Schedule power calculation after 500ms root.after(500, pow_update) def pow_update(): global rpm, tor, pow_val try: rpm_float = float(rpm) tor_float = float(tor) pow_val = int(rpm_float * tor_float / 5252) except ValueError: pass var4.set(pow_val) # Restart the full update cycle after 1 second root.after(1000, rpm_update) # GUI setup path = "/home/pi/logo.png" root = tk.Tk() img = ImageTk.PhotoImage(Image.open(path)) panel = tk.Label(root, image=img) panel.pack() panel.place(x=195,y=10,height=50,width=80) root.title("Dynalec") root.geometry("500x600") # Tkinter variables for display var = tk.StringVar(value=str(pos)) var2 = tk.StringVar(value=str(rpm)) var3 = tk.StringVar(value=str(tor)) var4 = tk.StringVar(value=str(pow_val)) # Example GUI elements (adjust to match your original interface) pos_label = tk.Label(root, text="Position:", font=('Arial', 14)) pos_label.pack(pady=5) pos_display = tk.Label(root, textvariable=var, font=('Arial', 20)) pos_display.pack(pady=5) rpm_label = tk.Label(root, text="RPM:", font=('Arial', 14)) rpm_label.pack(pady=5) rpm_display = tk.Label(root, textvariable=var2, font=('Arial', 20)) rpm_display.pack(pady=5) tor_label = tk.Label(root, text="Torque:", font=('Arial', 14)) tor_label.pack(pady=5) tor_display = tk.Label(root, textvariable=var3, font=('Arial', 20)) tor_display.pack(pady=5) pow_label = tk.Label(root, text="Power:", font=('Arial', 14)) pow_label.pack(pady=5) pow_display = tk.Label(root, textvariable=var4, font=('Arial', 20)) pow_display.pack(pady=5) # Initialize rotary decoder decoder = decoder(pi, 17, 18, callback) # Start the first update immediately root.after(0, rpm_update) root.mainloop()
Key Changes Explained
- Removed all
time.sleep()calls: No more blocking the Tkinter event loop—encoder interrupts can fire instantly now. - Chained
root.after()calls: Each update step schedules the next one, keeping the loop free to handle other events. - Variable name fix: Renamed
powtopow_valto avoid conflicting with Python’s built-inpow()function. - Simplified boundary logic: Used
max(0, min(pos, 9999))to clamp the encoder position more cleanly. - Explicit GUI elements: Added basic display labels to make the example runnable (adjust to match your original interface).
This setup will keep your encoder responsive while still updating your sensor values every second as needed.
内容的提问来源于stack exchange,提问作者ram

