Python 按钮功能奇怪地不一样
Python button functions oddly not doing the same
我目前有 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()
从表面上看,这是一个相当简单的脚本。当检测到按钮按下时:
- 删除事件
- 打印消息
- 等待 2 秒,然后添加事件并重新打开 LED
当我按下绿色按钮时,通常效果很好。我连续试了几次,都没有失败。然而,对于红色,它第一次和第二次都运行良好,但在它完成第二个红色(pin)循环后脚本就停止了。
考虑到这两个事件非常相似,我无法解释为什么它在第二个红色按钮结束时失败。
编辑:我已经分别将引脚从红色和绿色更改为(完全更改为不同的引脚或交换它们)。无论哪种方式,它总是红色按钮代码(实际上现在是绿色按钮)导致错误。所以它似乎不是物理红色按钮问题,也不是引脚问题,这只是代码有问题...
我能够通过 运行 你的脚本在我的 Raspberry Pi 1 模型 B 上重现你的问题,并在接地和 GPIO27 之间连接一根跨接电缆来模拟红色按钮按下。 (这些是我的特定 Pi 型号上的引脚 25 和 13。)
python 解释器在处理按钮按下 red
return 秒后,在专用于轮询 GPIO 事件的线程中出现分段错误而崩溃。在查看 Python GPIO
模块的实现后,我很清楚从事件处理程序回调中调用 remove_event_detect
是不安全的,这导致了崩溃。特别是,在事件处理程序当前为 运行 时删除事件处理程序可能会导致内存损坏,这将导致崩溃(如您所见)或其他奇怪的行为。
我怀疑您正在删除并重新添加事件处理程序,因为您担心在按下按钮时收到回调。没有必要这样做。 GPIO 模块启动一个轮询线程来监视 GPIO 事件,并会等待一个回调到 return,然后再调用另一个回调,无论您正在观察的 GPIO 事件有多少。
我建议您只在脚本启动时调用 add_event_detect
,不要删除回调。只需从脚本中删除 add_events
和 remove_events
(及其调用)即可解决问题。
如果你对GPIO
模块中的问题详情感兴趣,可以看看C source code for that module。查看文件 RPi.GPIO-0.6.2/source/event_gpio.c
中的 run_callbacks
和 remove_callbacks
。请注意,这两个函数都使用 struct callback
个节点的全局链。 run_callbacks
通过抓取一个节点、调用回调,然后跟随该节点的 link 到链中的下一个回调来遍历回调链。 remove_callbacks
将遍历相同的回调链,并释放与特定 GPIO 引脚上的回调关联的内存。如果在 run_callbacks
中间调用 remove_callbacks
,则可以在指向下一个节点的指针之后释放当前由 run_callbacks
持有的节点(并可能重用和覆盖其内存) .
您只看到红色按钮出现此问题的原因可能是调用 add_event_detect
和 remove_event_detect
的顺序导致红色按钮的回调节点之前使用的内存出于某些其他目的而被回收并在绿色按钮回调节点使用的内存被类似地回收之前被覆盖。但是,请放心,这两个按钮都存在问题——幸运的是,在指向下一个回调节点的指针被跟踪之前,与绿色按钮回调关联的内存没有改变。
更一般地说,在 GPIO 模块中使用的回调链周围普遍缺乏线程同步,我怀疑如果在调用 remove_event_detect
或 add_event_detect
时可能会出现类似的问题事件处理程序是 运行,即使事件已从另一个线程中删除!我建议 RPi.GPIO
模块的作者应该使用一些同步来确保在进行回调时不能修改回调链。 (也许,除了检查轮询线程本身是否正在修改链之外,pthread_mutex_lock
和 pthread_mutex_unlock
可用于防止其他线程在轮询使用回调链时修改它线程。)
不幸的是,目前情况并非如此,因此我建议您尽可能避免调用 remove_event_detect
。
我目前有 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()
从表面上看,这是一个相当简单的脚本。当检测到按钮按下时:
- 删除事件
- 打印消息
- 等待 2 秒,然后添加事件并重新打开 LED
当我按下绿色按钮时,通常效果很好。我连续试了几次,都没有失败。然而,对于红色,它第一次和第二次都运行良好,但在它完成第二个红色(pin)循环后脚本就停止了。
考虑到这两个事件非常相似,我无法解释为什么它在第二个红色按钮结束时失败。
编辑:我已经分别将引脚从红色和绿色更改为(完全更改为不同的引脚或交换它们)。无论哪种方式,它总是红色按钮代码(实际上现在是绿色按钮)导致错误。所以它似乎不是物理红色按钮问题,也不是引脚问题,这只是代码有问题...
我能够通过 运行 你的脚本在我的 Raspberry Pi 1 模型 B 上重现你的问题,并在接地和 GPIO27 之间连接一根跨接电缆来模拟红色按钮按下。 (这些是我的特定 Pi 型号上的引脚 25 和 13。)
python 解释器在处理按钮按下 red
return 秒后,在专用于轮询 GPIO 事件的线程中出现分段错误而崩溃。在查看 Python GPIO
模块的实现后,我很清楚从事件处理程序回调中调用 remove_event_detect
是不安全的,这导致了崩溃。特别是,在事件处理程序当前为 运行 时删除事件处理程序可能会导致内存损坏,这将导致崩溃(如您所见)或其他奇怪的行为。
我怀疑您正在删除并重新添加事件处理程序,因为您担心在按下按钮时收到回调。没有必要这样做。 GPIO 模块启动一个轮询线程来监视 GPIO 事件,并会等待一个回调到 return,然后再调用另一个回调,无论您正在观察的 GPIO 事件有多少。
我建议您只在脚本启动时调用 add_event_detect
,不要删除回调。只需从脚本中删除 add_events
和 remove_events
(及其调用)即可解决问题。
如果你对GPIO
模块中的问题详情感兴趣,可以看看C source code for that module。查看文件 RPi.GPIO-0.6.2/source/event_gpio.c
中的 run_callbacks
和 remove_callbacks
。请注意,这两个函数都使用 struct callback
个节点的全局链。 run_callbacks
通过抓取一个节点、调用回调,然后跟随该节点的 link 到链中的下一个回调来遍历回调链。 remove_callbacks
将遍历相同的回调链,并释放与特定 GPIO 引脚上的回调关联的内存。如果在 run_callbacks
中间调用 remove_callbacks
,则可以在指向下一个节点的指针之后释放当前由 run_callbacks
持有的节点(并可能重用和覆盖其内存) .
您只看到红色按钮出现此问题的原因可能是调用 add_event_detect
和 remove_event_detect
的顺序导致红色按钮的回调节点之前使用的内存出于某些其他目的而被回收并在绿色按钮回调节点使用的内存被类似地回收之前被覆盖。但是,请放心,这两个按钮都存在问题——幸运的是,在指向下一个回调节点的指针被跟踪之前,与绿色按钮回调关联的内存没有改变。
更一般地说,在 GPIO 模块中使用的回调链周围普遍缺乏线程同步,我怀疑如果在调用 remove_event_detect
或 add_event_detect
时可能会出现类似的问题事件处理程序是 运行,即使事件已从另一个线程中删除!我建议 RPi.GPIO
模块的作者应该使用一些同步来确保在进行回调时不能修改回调链。 (也许,除了检查轮询线程本身是否正在修改链之外,pthread_mutex_lock
和 pthread_mutex_unlock
可用于防止其他线程在轮询使用回调链时修改它线程。)
不幸的是,目前情况并非如此,因此我建议您尽可能避免调用 remove_event_detect
。