Python RaspberryPi GPIO 事件检测在 Tkinter 中失败

1 投票
1 回答
3500 浏览
提问于 2025-04-18 14:50

我在用Python和Tkinter在树莓派上检测GPIO事件时遇到了一个奇怪的问题。

当我点击startGameButton按钮时,它会调用start_game这个函数。在这个函数里,我在try块中添加了一个GPIO事件,并且有一个循环会运行30秒。在这段时间里,我期待在23号引脚上会发生GPIO.Falling事件,每当这个事件发生时,p2ScoreEvent函数应该会被执行。实际上发生的情况是,这个事件似乎只在第一次发生时被触发,如果我不断制造GPIO.Falling事件,直到循环结束都不会有任何反应。循环结束后,如果事件发生了多次,p2ScoreEvent函数会被调用第二次,但就只有这么一次。

一旦我走出start_game中的那个循环,事件检测就完全正常了。我还确认了这一部分:

                try:
                GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)
                while (time.time() - start_time) < game_time
                    print "listening"
                    time.sleep(5)
            except:
                    print "Something went wrong..."
                    GPIO.cleanup()

在命令行中单独运行时是正常的。

这是让我遇到问题的完整代码片段:

from Tkinter import *
import time
import RPi.GPIO as GPIO

class App:
        def p2ScoreEvent(self, p1pin):
                print "ScoreEvent"
                global p2score
                p2score = p2score + 1
                p2diag.set(p2diagString + repr(p2score))

        def start_game(self):
                global p2score
                start_time = time.time()
                game_time = 30      #length of game
                P1PIN = 23
                GPIO.setmode(GPIO.BCM)
                GPIO.setup(P1PIN, GPIO.IN)
                GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)

                try:
                    GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)
                    while (time.time() - start_time) < game_time
                        print "listening"
                        time.sleep(5)
                except:
                        print "Something went wrong..."
                        GPIO.cleanup()



        def __init__(self, master):
                frame = Frame(master)

                global p2diagString
                p2diagString = "Player 2 Score: "
                global p2score

                p2score = 0

                global p2diag
                p2diag = StringVar()
                p2diag.set(p2diagString + repr(p2score))

                p2Label = Label(root, fg="white", bg="blue", textvariable=p2diag).grid(row=1, column=1)

                self.startGameButton = Button(
                        root, text="Start Game!", command=self.start_game
                        )
                self.startGameButton.grid(row=3, columnspan=2)


root = Tk()
app = App(root)
root.mainloop()

我在想这可能和调用start_game函数有关,但我不太确定具体是什么。我对Python的经验不多,所以有点难以理解到底发生了什么。

为什么GPIO事件只在第一次发生时被触发,而如果它实际上发生了超过两次,为什么在循环结束时只触发一次?

1 个回答

1

mainloop() 是程序中负责处理所有工作的部分 - 它会一个接一个地运行事件函数(以及其他函数),看起来就像是在同时处理多个任务。但如果有哪个函数运行得太久(比如使用了 while Truetime.sleep()),那么 mainloop 就无法执行其他函数了。

所以,不要使用 time.sleep() 和长时间运行的循环,而是应该使用 root.after(time, function) 来重复运行某个函数。

我不能测试这个,但它可能看起来像这样:

def my_loop(self):
    if (time.time() - self.start_time) < self.game_time:
       print "listening"
       root.after(5000, self.my_loop) # run my_loop again after 5000 milliseconds

def start_game(self):
        global p2score

        # use self. to get access in other function
        self.start_time = time.time()
        self.game_time = 30      #length of game

        P1PIN = 23
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(P1PIN, GPIO.IN)
        GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)

        try:
            GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)
            self.my_loop() # run my_loop first time
        except:
            print "Something went wrong..."
            GPIO.cleanup()

顺便说一下:

你可以使用 self.end_time 来减少计算量。

def my_loop(self):
    if time.time() < self.end_time:
       print "listening"
       root.after(5000, self.my_loop) # run my_loop again after 5000 milliseconds

def start_game(self):
        global p2score

        # use self. to get access in other function
        # self.game_time = 30 
        self.end_time = time.time() + 30

顺便说一下:

我们使用 classesself. 来避免使用 global

所有代码可能看起来像这样:

from Tkinter import *
import time
import RPi.GPIO as GPIO

class App():

    def __init__(self, master):

        self.master = master

        self.frame = Frame(master)

        self.p2diagString = "Player 2 Score: "
        self.p2score = 0

        self.p2diag = StringVar()
        self.p2diag.set(self.p2diagString + str(self.p2score))

        p2Label = Label(self.frame, fg="white", bg="blue", textvariable=self.p2diag)
        p2Label.grid(row=1, column=1)

        self.startGameButton = Button(
            self.frame, text="Start Game!", command=self.start_game
        )
        self.startGameButton.grid(row=3, columnspan=2)

    def p2ScoreEvent(self, p1pin):
        print "ScoreEvent"

        self.p2score += 1
        self.p2diag.set(self.p2diagString + str(self.p2score))

    def start_game(self):
        self.game_time = 30
        self.end_time = time.time() + self.game_time

        P1PIN = 23
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(P1PIN, GPIO.IN)
        GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)

        try:
            GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)
            self.my_loop()
        except:
            print "Something went wrong..."
            GPIO.cleanup()

    def my_loop(self):
        if time.time() < self.end_time:
           print "listening"
           root.after(5000, self.my_loop) # run my_loop again after 5000 milliseconds

    def run(self):
        self.master.mainloop()

#----------------------------------------------------------------------

App(Tk()).run()

我把 __init__ 放在类的第一个函数位置 - 这样更容易阅读 - 大家都习惯在类的开头看到 __init__

我用 str() 替代了 repl()

在类里面,我不使用外部变量,所有变量都放在内部。

撰写回答