Python按钮功能奇怪地没有做同样的事情

时间:2016-09-30 07:03:15

标签: python raspberry-pi gpio

我目前有两个连接到我的Raspberry Pi的按钮(这些是带有LED指示灯的那些按钮)并且我试图执行此代码

#!/usr/bin/env python
import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(17, GPIO.OUT) #green LED
GPIO.setup(18, GPIO.OUT) #red LED
GPIO.setup(4, GPIO.IN, GPIO.PUD_UP) #green button
GPIO.setup(27, GPIO.IN, GPIO.PUD_UP) #red button

def remove_events():
        GPIO.remove_event_detect(4)
        GPIO.remove_event_detect(27)

def add_events():
        GPIO.add_event_detect(4, GPIO.FALLING, callback=green, bouncetime=800)
        GPIO.add_event_detect(27, GPIO.FALLING, callback=red, bouncetime=800)

def red(pin):
        remove_events()
        GPIO.output(17, GPIO.LOW)
        print "red pushed"
        time.sleep(2)
        GPIO.output(17, GPIO.HIGH)
        add_events()

def green(pin):
        remove_events()
        GPIO.output(18, GPIO.LOW)
        print "green pushed"
        time.sleep(2)
        GPIO.output(18, GPIO.HIGH)
        add_events()

def main():
    while True:
        print "waiting"
        time.sleep(0.5)

GPIO.output(17, GPIO.HIGH)
GPIO.output(18, GPIO.HIGH)
GPIO.add_event_detect(4, GPIO.FALLING, callback=green, bouncetime=800)
GPIO.add_event_detect(27, GPIO.FALLING, callback=red, bouncetime=800)

if __name__ == "__main__":
    main()

从表面上看,它看起来像一个相当简单的脚本。当检测到按下按钮时:

  1. 删除活动
  2. 打印消息
  3. 等待2秒后再添加事件并重新打开LED
  4. 当我按下绿色按钮时,通常效果很好。我连续几次试了几次,但它确实有效。然而,使用红色,它第一次运行良好,第二次运行良好,但在完成第二次红色(引脚)循环后脚本停止运行。

    考虑到这两个事件非常相似,我无法解释为什么它在第二个红色按钮结束时失败。

    编辑:我已经分别从红色和绿色更改了引脚(要么完全更换到不同的引脚,要么交换它们)。无论哪种方式,它始终是红色按钮代码(实际上现在是绿色按钮)会导致错误。所以它似乎是'不是物理红色按钮问题,也不是引脚问题,这只会让代码出现问题......

1 个答案:

答案 0 :(得分:9)

通过运行脚本并在地面和GPIO27之间连接跳线以模拟按下红色按钮,我能够在我的Raspberry Pi 1,Model B上重现您的问题。 (这是我特定Pi模型上的引脚25和13。)

在处理按钮按下后red返回后,python解释器在专用于轮询GPIO事件的线程中崩溃并出现Segmentation Fault。在查看Python GPIO模块的实现之后,我很清楚从事件处理程序回调中调用remove_event_detect是不安全的,这会导致崩溃。特别是,当事件处理程序当前正在运行时删除事件处理程序可能会导致内存损坏,这将导致崩溃(如您所见)或其他奇怪的行为。

我怀疑您正在删除并重新添加事件处理程序,因为您担心在按下按钮时会收到回调。没有必要这样做。 GPIO模块旋转一个轮询线程来监视GPIO事件,并且在调用另一个回调之前等待一个回调返回,无论您正在观看的GPIO事件的数量是多少。

我建议您在脚本启动时简单地调用add_event_detect,并且永远不要删除回调。只需从脚本中删除add_eventsremove_events(及其调用)即可解决问题。

如果您对GPIO模块中的问题详情感兴趣,可以查看C source code for that module。请查看文件run_callbacks中的remove_callbacksRPi.GPIO-0.6.2/source/event_gpio.c。请注意,这两个函数都使用struct callback个节点的全局链。 run_callbacks通过抓取一个节点,调用回调,然后跟随该节点链接到链中的下一个回调来遍历回调链。 remove_callbacks将走同一个回调链,并释放与特定GPIO引脚上的回调相关的内存。如果在remove_callbacks的中间调用run_callbacks,则在遵循指向下一个节点的指针之前,可以释放当前由run_callbacks持有的节点(并且可以重用和覆盖其内存)

您仅为红色按钮看到此问题的原因可能是由于调用add_event_detectremove_event_detect的顺序导致回调节点先前用于回收红色按钮的内存出于某些其他目的,并且比绿色按钮回调节点使用的内存更早地覆盖,同样回收。但是,请确保两个按钮都存在问题 - 幸运的是,在遵循指向下一个回调节点的指针之前,与绿色按钮回调相关联的内存未被更改。

更一般地说,GPIO模块中回调链的使用缺乏线程同步,我怀疑在事件中调用remove_event_detectadd_event_detect时可能会出现类似的问题即使从另一个线程中删除了事件,处理程序也在运行!我建议RPi.GPIO模块的作者应该使用一些同步来确保在进行回调时不能修改回调链。 (也许,除了检查是否在轮询线程本身上修改链之外,还可以使用pthread_mutex_lockpthread_mutex_unlock来防止其他线程在轮询使用时修改回调链线程。)

不幸的是,目前情况并非如此,因此我建议您不要完全致电remove_event_detect,如果可以避免的话。