如何实现树莓派GPIO输入按键的异步运行?
嘿,我来帮你搞定树莓派GPIO按键的异步响应问题!你的同步代码里,subprocess.call()会阻塞主线程直到命令执行完毕——这就是为什么播放音频时没法响应其他按键的原因。下面给你几个实用的解决方案,按推荐程度排序:
方案1:用RPi.GPIO的边缘事件检测(最优雅高效)
RPi.GPIO原生支持边缘检测,不用写死循环轮询GPIO状态,而是让系统在按键状态变化时自动触发回调函数,完美实现异步响应。
修改后的代码如下:
import RPi.GPIO as GPIO import subprocess import threading # 定义全局变量,防止重复触发同一命令(比如多次按播放键启动多个音频进程) is_playing = False is_scanning = False def scan_button_callback(channel): global is_scanning if not is_scanning: is_scanning = True print('Scan Button Pressed') # 用Popen启动子进程,不阻塞主线程 subprocess.Popen(['./ocr.sh']) is_scanning = False # 若ocr.sh执行耗时久,可改用线程等待进程结束后再重置 def play_button_callback(channel): global is_playing if not is_playing: is_playing = True print('Read Button Pressed') # 启动音频播放进程,不阻塞 proc = subprocess.Popen(['aplay','temp1.wav']) # 用线程等待音频播放完毕,重置标志位避免重复播放 def reset_play_flag(): proc.wait() global is_playing is_playing = False threading.Thread(target=reset_play_flag, daemon=True).start() # GPIO初始化 GPIO.setmode(GPIO.BCM) # 设置上拉电阻,按键按下时输入为低电平 GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP) # 添加边缘检测:检测FALLING边缘(从高到低,即按键按下),bouncetime消除按键抖动 GPIO.add_event_detect(18, GPIO.FALLING, callback=scan_button_callback, bouncetime=300) GPIO.add_event_detect(17, GPIO.FALLING, callback=play_button_callback, bouncetime=300) # 保持主线程运行 try: while True: pass # 主线程可在此执行其他逻辑,或仅挂起 except KeyboardInterrupt: GPIO.cleanup() # 退出前清理GPIO资源 print('程序已退出')
关键说明:
GPIO.add_event_detect:注册按键状态变化的回调,bouncetime参数用于消除机械按键的抖动,避免一次按下触发多次回调。subprocess.Popen:替代call(),启动子进程后立即返回,不会阻塞主线程。- 全局标志位:防止同一命令被重复触发(比如音频还在播放时再次按播放键),用线程等待进程结束后重置标志位。
方案2:替换subprocess.call为Popen(最小改动)
如果你不想大幅修改原有代码,只需要把阻塞式的subprocess.call()换成非阻塞的subprocess.Popen(),主线程就能继续轮询按键状态:
import RPi.GPIO as GPIO import time import subprocess import threading GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP) # 标志位防止重复触发 playing = False scanning = False while True: input_state = GPIO.input(18) input_state1 = GPIO.input(17) if input_state == False and not scanning: scanning = True print('Scan Button Pressed') subprocess.Popen(['./ocr.sh']) scanning = False # 若ocr.sh执行耗时久,可改用线程等待后重置 if input_state1 == False and not playing: playing = True print('Read Button Pressed') proc = subprocess.Popen(['aplay','temp1.wav']) # 线程等待音频播放完毕,重置标志位 def wait_complete(): proc.wait() global playing playing = False threading.Thread(target=wait_complete, daemon=True).start() time.sleep(0.1) # 降低轮询频率,减少CPU占用
注意:
- 这个方案仍使用轮询机制,CPU占用比边缘检测略高,但代码改动最小。
- 必须添加标志位,否则多次按键会启动多个相同进程(比如同时播放多段音频)。
方案3:用Python线程封装处理逻辑
把每个按键的处理逻辑放到独立线程中,主线程检测到按键按下时启动对应线程:
import RPi.GPIO as GPIO import time import subprocess import threading GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP) def handle_scan(): print('Scan Button Pressed') subprocess.call(['./ocr.sh']) # 此处用call也无妨,因为在子线程中执行,不阻塞主线程 def handle_play(): print('Read Button Pressed') subprocess.call(['aplay','temp1.wav']) while True: input_state = GPIO.input(18) input_state1 = GPIO.input(17) if input_state == False: threading.Thread(target=handle_scan, daemon=True).start() time.sleep(0.3) # 消除按键抖动,避免一次按下触发多次线程 if input_state1 == False: threading.Thread(target=handle_play, daemon=True).start() time.sleep(0.3) time.sleep(0.1)
说明:
- 每个处理逻辑在独立线程中执行,主线程可继续检测其他按键。
daemon=True确保线程随主线程一起退出,避免程序残留后台进程。time.sleep(0.3)用于消除机械按键的抖动问题,防止一次按下触发多次线程。
内容的提问来源于stack exchange,提问作者Dusayanta Prasad
相关产品推荐
相关产品推荐

