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

使用tkinter的root.after()后旋转编码器值卡顿的技术咨询

Fixing Rotary Encoder Stutter & Blocked Interrupts with Tkinter's 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

  1. Removed all time.sleep() calls: No more blocking the Tkinter event loop—encoder interrupts can fire instantly now.
  2. Chained root.after() calls: Each update step schedules the next one, keeping the loop free to handle other events.
  3. Variable name fix: Renamed pow to pow_val to avoid conflicting with Python’s built-in pow() function.
  4. Simplified boundary logic: Used max(0, min(pos, 9999)) to clamp the encoder position more cleanly.
  5. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 10:37:44