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

基于Tkinter的秒表紧急停止按钮释放功能故障排查

解决Tkinter秒表紧急暂停/恢复功能异常问题

功能需求

  • Start按钮:启动计时,画布变为绿色(运行中);计时达到15秒时画布变为红色
  • Stop按钮:重置计时,画布变为黄色(空闲状态)
  • Emergency Stop按钮:按下时暂停计时(画布变为橙色),释放时恢复计时(若计时未达15秒,画布变回绿色)

当前问题

Start与Stop按钮功能正常,但Emergency Stop存在以下问题:

  • 启动计时5秒后按下该按钮,计时会暂停,但释放按钮后程序陷入打印“Released”的死循环,无法恢复计时
  • 进入Tkinter主循环后,难以追踪按钮状态与程序流程,调试困难

问题分析

  1. 阻塞Tkinter主循环:原pause函数中使用while循环+time.sleep(),会彻底卡住Tkinter的事件循环,导致界面无响应,也无法正确处理后续的GPIO释放事件
  2. 语法错误:代码中用了eStop_resume == True(比较运算符)而非eStop_resume = True(赋值运算符),导致内层循环无法退出
  3. 事件处理逻辑错误:紧急暂停需要分别处理「按下」和「释放」两个事件,而非在同一个回调里用死循环等待释放动作

修复后的完整代码

import tkinter as tk
import time
import RPi.GPIO as GPIO

class StopWatch(tk.Frame):
    def __init__(self, parent=None, **kw):
        tk.Frame.__init__(self, parent, kw)
        self._start = 0.0
        self._elapsedtime = 0.0
        self._running = 0
        
        self._timer_text = tk.StringVar()
        self._timer_text.set("00:00:00")
        self.grid(sticky=tk.NSEW)
        
        self.timer_label = tk.Label(self, textvariable=self._timer_text, font=("Helvetica", 48))
        self.timer_label.grid(row=0, column=1, pady=10, padx=10)

        self.canvas = tk.Canvas(self, width=100, height=100, bg="yellow", highlightthickness=0)
        self.canvas.grid(row=0, column=0, pady=10, padx=10)

        # 绑定Start和Stop的按下事件
        GPIO.add_event_detect(14, GPIO.FALLING, callback=self.start, bouncetime=50)
        GPIO.add_event_detect(15, GPIO.FALLING, callback=self.reset, bouncetime=50)
        # 为Emergency Stop同时绑定按下(暂停)和释放(恢复)事件
        GPIO.add_event_detect(21, GPIO.FALLING, callback=self.emergency_pause, bouncetime=200)
        GPIO.add_event_detect(21, GPIO.RISING, callback=self.emergency_resume, bouncetime=200)

    def _update(self):
        """Update the timer label with elapsed time."""
        if not self._running:
            return
        self._elapsedtime = time.time() - self._start
        self._timer_text.set(time.strftime('%H:%M:%S', time.gmtime(self._elapsedtime)))
        # 根据计时状态更新画布颜色
        if self._elapsedtime > 15:
            self.canvas.config(bg='red')
        else:
            # 只有在运行中且未到15秒时才保持绿色
            if self._running:
                self.canvas.config(bg='green')
        # 继续调度下一次更新
        self.after(100, self._update)

    def start(self, channel):
        """Start the stopwatch."""
        self._start = time.time() - self._elapsedtime
        self._running = 1
        # 启动时根据是否超过15秒设置颜色
        if self._elapsedtime > 15:
            self.canvas.config(bg='red')
        else:
            self.canvas.config(bg='green')
        self._update()

    def emergency_pause(self, channel):
        """Emergency pause the stopwatch when button is pressed."""
        if self._running:
            self._running = 0
            self.canvas.config(bg='orange')
            print("Emergency paused")

    def emergency_resume(self, channel):
        """Resume the stopwatch when emergency button is released."""
        if not self._running:
            self._start = time.time() - self._elapsedtime
            self._running = 1
            # 恢复时根据是否超过15秒设置颜色
            if self._elapsedtime > 15:
                self.canvas.config(bg='red')
            else:
                self.canvas.config(bg='green')
            self._update()
            print("Resumed")
            
    def reset(self, channel):
        """Reset the stopwatch."""
        self._start = time.time()
        self._elapsedtime = 0.0
        self._timer_text.set("00:00:00")
        self._running = 0
        self.canvas.config(bg='yellow')
        print("Reset")
        
    
if __name__ == '__main__':
    # 初始化GPIO设置
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(14, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(15, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(21, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    
    root = tk.Tk()
    root.title('Stopwatch')
    root.geometry('500x200')
    stopwatch = StopWatch(root)
    stopwatch.pack(expand=True, fill=tk.BOTH)
    
    try:
        root.mainloop()
    finally:
        # 清理GPIO资源
        GPIO.cleanup()

关键修改说明

  1. 拆分紧急暂停/恢复逻辑:将原来的pause函数拆分为emergency_pause(处理按钮按下)和emergency_resume(处理按钮释放),分别绑定GPIO的FALLING和RISING事件
  2. 移除阻塞操作:删除所有while循环和time.sleep(),改用Tkinter的after()方法实现计时更新,避免阻塞主循环
  3. 修正颜色逻辑:恢复计时时根据当前已流逝时间判断画布颜色(超过15秒保持红色,否则变回绿色)
  4. 添加GPIO初始化与清理:完善GPIO的模式设置、输入引脚配置,程序退出时自动清理GPIO资源

调试建议

  • 状态打印:在关键函数(start/emergency_pause/emergency_resume/reset)中添加print()语句,输出_running、_elapsedtime等核心变量的值,追踪程序流程
  • 避免阻塞主循环:所有需要周期性执行的操作都用Tkinter的after()方法实现,绝对不要在事件回调中使用time.sleep()或死循环
  • 使用logging模块:如果需要更系统的调试日志,可以用Python的logging模块替代print(),方便控制日志级别和输出位置

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 23:43:17