【问题标题】:Python button functions oddly not doing the samePython按钮功能奇怪地不一样
【发布时间】:2017-02-08 16:20:51
【问题描述】:

我目前有 2 个按钮连接到我的 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. 在添加事件并重新打开 LED 之前等待 2 秒

当我按下绿色按钮时,通常效果很好。我连续试了好几次,都成功了。然而,对于红色,它第一次运行良好,第二次运行良好,但在完成第二次红色(pin)循环后,脚本就停止了。

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

编辑:我已分别将引脚从红色和绿色更改(完全更改为不同的引脚或交换它们)。无论哪种方式,它总是红色按钮代码(实际上现在是绿色按钮)导致错误。所以看起来它不是物理红色按钮问题,也不是引脚问题,这只会让代码出错......

【问题讨论】:

  • 也许其中一个GPIO.output 调用引发了异常,然后add_events() 再也没有被调用?
  • 感谢您对此事的看法。我添加了 except 子句,但它们没有被触发。看起来不是这个。
  • 它也无法解释为什么它一次运行良好但在第二个周期结束时总是失败......
  • 此代码没有显示我可以看到的任何其他可能性。这就是你拥有的所有代码吗?您是否尝试过其他输出引脚(17 和 18 除外)?您是否尝试过其他输入引脚(4 和 27 除外)?如果您交替点击 1 次绿色点击 - 1 次红色点击等会发生什么。在第二次点击红色或第二次绿色点击后它是否仍然停止响应?你能看到一些串行监视器上的打印吗?如果是,请添加更多(例如,在每一行之后),以便您知道问题之前的确切路径。
  • 这就是我拥有的所有代码。我也试过不同的别针,是的。结果似乎惊人地相同。我也交替了,无论我尝试什么,它似乎总是停在红色的第二个周期(即 1 个红色,10 个绿色,然后再有 1 个红色)我还添加了打印行以查看它出错的地方和它一直到最后一行代码,所以我真的很困惑......

标签: python raspberry-pi gpio


【解决方案1】:

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

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

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

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

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

您仅在红色按钮上看到此问题的原因可能是由于对 add_event_detectremove_event_detect 的调用顺序导致回调节点先前用于红色按钮的内存被回收用于其他目的并且在绿色按钮回调节点使用的内存之前被覆盖并被类似地回收。但是,请放心,这两个按钮都存在问题——幸运的是,与绿色按钮回调关联的内存在指向下一个回调节点的指针被跟随之前没有改变。

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

很遗憾,目前情况并非如此,因此我建议您在可以避免的情况下完全避免致电remove_event_detect

【讨论】:

  • 良好的调试。您可能会提到如何在段错误中捕获解释器,但除此之外它是非常完整的信息。部分原因是 GPIO 一次只服务一个事件,所以在事件处理程序中休眠并不是一个好主意。解决方案是设置事件结束时间,然后继续扫描。避免多个事件的方法是使用 test-and-swap 命令,在 ARMv6 (Raspberry Pi 1) 及更高版本上是 STREX 和 LDREX:infocenter.arm.com/help/topic/com.arm.doc.dht0008a/… 也许对于 Pi 和 Python,有一个包装器。
猜你喜欢
  • 2019-11-15
  • 2021-04-01
  • 1970-01-01
  • 2018-04-30
  • 1970-01-01
  • 2017-01-04
  • 1970-01-01
  • 1970-01-01
  • 2013-02-27
相关资源
最近更新 更多